✨ Social credit validation and recalculation
This commit is contained in:
@@ -1,10 +1,12 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using DysonNetwork.Pass.Auth;
|
using DysonNetwork.Pass.Auth;
|
||||||
using DysonNetwork.Pass.Credit;
|
using DysonNetwork.Pass.Credit;
|
||||||
|
using DysonNetwork.Pass.Permission;
|
||||||
using DysonNetwork.Pass.Wallet;
|
using DysonNetwork.Pass.Wallet;
|
||||||
using DysonNetwork.Shared.GeoIp;
|
using DysonNetwork.Shared.GeoIp;
|
||||||
using DysonNetwork.Shared.Http;
|
using DysonNetwork.Shared.Http;
|
||||||
using DysonNetwork.Shared.Models;
|
using DysonNetwork.Shared.Models;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using NodaTime;
|
using NodaTime;
|
||||||
@@ -266,4 +268,13 @@ public class AccountController(
|
|||||||
.Take(take)
|
.Take(take)
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
[HttpPost("credits/validate")]
|
||||||
|
[Authorize]
|
||||||
|
[RequiredPermission("maintenance", "credits.validate.perform")]
|
||||||
|
public async Task<IActionResult> PerformSocialCreditValidation()
|
||||||
|
{
|
||||||
|
await socialCreditService.ValidateSocialCredits();
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using DysonNetwork.Shared.Cache;
|
using DysonNetwork.Shared.Cache;
|
||||||
using DysonNetwork.Shared.Models;
|
using DysonNetwork.Shared.Models;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using NodaTime;
|
||||||
|
|
||||||
namespace DysonNetwork.Pass.Credit;
|
namespace DysonNetwork.Pass.Credit;
|
||||||
|
|
||||||
@@ -35,13 +36,37 @@ public class SocialCreditService(AppDatabase db, ICacheService cache)
|
|||||||
{
|
{
|
||||||
var cached = await cache.GetAsync<double?>($"{CacheKeyPrefix}{accountId}");
|
var cached = await cache.GetAsync<double?>($"{CacheKeyPrefix}{accountId}");
|
||||||
if (cached.HasValue) return cached.Value;
|
if (cached.HasValue) return cached.Value;
|
||||||
|
|
||||||
var records = await db.SocialCreditRecords
|
var records = await db.SocialCreditRecords
|
||||||
.Where(x => x.AccountId == accountId)
|
.Where(x => x.AccountId == accountId && x.Status == SocialCreditRecordStatus.Active)
|
||||||
.SumAsync(x => x.Delta);
|
.SumAsync(x => x.Delta);
|
||||||
records += BaseSocialCredit;
|
records += BaseSocialCredit;
|
||||||
|
|
||||||
await cache.SetAsync($"{CacheKeyPrefix}{accountId}", records);
|
await cache.SetAsync($"{CacheKeyPrefix}{accountId}", records);
|
||||||
return records;
|
return records;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
public async Task ValidateSocialCredits()
|
||||||
|
{
|
||||||
|
var now = SystemClock.Instance.GetCurrentInstant();
|
||||||
|
var expiredRecords = await db.SocialCreditRecords
|
||||||
|
.Where(r => r.Status == SocialCreditRecordStatus.Active && r.ExpiredAt.HasValue && r.ExpiredAt <= now)
|
||||||
|
.Select(r => new { r.Id, r.AccountId, r.Delta })
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
var groupedExpired = expiredRecords.GroupBy(er => er.AccountId)
|
||||||
|
.ToDictionary(g => g.Key, g => g.Sum(er => er.Delta));
|
||||||
|
|
||||||
|
foreach (var (accountId, totalDeltaSubtracted) in groupedExpired)
|
||||||
|
{
|
||||||
|
await db.AccountProfiles
|
||||||
|
.Where(p => p.AccountId == accountId)
|
||||||
|
.ExecuteUpdateAsync(p => p.SetProperty(v => v.SocialCredits, v => v.SocialCredits - totalDeltaSubtracted));
|
||||||
|
await cache.RemoveAsync($"{CacheKeyPrefix}{accountId}");
|
||||||
|
}
|
||||||
|
|
||||||
|
await db.SocialCreditRecords
|
||||||
|
.Where(r => r.Status == SocialCreditRecordStatus.Active && r.ExpiredAt.HasValue && r.ExpiredAt <= now)
|
||||||
|
.ExecuteUpdateAsync(r => r.SetProperty(x => x.Status, SocialCreditRecordStatus.Expired));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
21
DysonNetwork.Pass/Credit/SocialCreditValidationJob.cs
Normal file
21
DysonNetwork.Pass/Credit/SocialCreditValidationJob.cs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
using Quartz;
|
||||||
|
|
||||||
|
namespace DysonNetwork.Pass.Credit;
|
||||||
|
|
||||||
|
public class SocialCreditValidationJob(SocialCreditService socialCreditService, ILogger<SocialCreditValidationJob> logger) : IJob
|
||||||
|
{
|
||||||
|
public async Task Execute(IJobExecutionContext context)
|
||||||
|
{
|
||||||
|
logger.LogInformation("Starting social credit validation...");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await socialCreditService.ValidateSocialCredits();
|
||||||
|
logger.LogInformation("Social credit validation completed successfully.");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
logger.LogError(ex, "Error occurred during social credit validation.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
2714
DysonNetwork.Pass/Migrations/20251101175500_AddSocialCreditRecordStatus.Designer.cs
generated
Normal file
2714
DysonNetwork.Pass/Migrations/20251101175500_AddSocialCreditRecordStatus.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,29 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace DysonNetwork.Pass.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddSocialCreditRecordStatus : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<int>(
|
||||||
|
name: "status",
|
||||||
|
table: "social_credit_records",
|
||||||
|
type: "integer",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "status",
|
||||||
|
table: "social_credit_records");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1601,6 +1601,10 @@ namespace DysonNetwork.Pass.Migrations
|
|||||||
.HasColumnType("character varying(1024)")
|
.HasColumnType("character varying(1024)")
|
||||||
.HasColumnName("reason_type");
|
.HasColumnName("reason_type");
|
||||||
|
|
||||||
|
b.Property<int>("Status")
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasColumnName("status");
|
||||||
|
|
||||||
b.Property<Instant>("UpdatedAt")
|
b.Property<Instant>("UpdatedAt")
|
||||||
.HasColumnType("timestamp with time zone")
|
.HasColumnType("timestamp with time zone")
|
||||||
.HasColumnName("updated_at");
|
.HasColumnName("updated_at");
|
||||||
|
|||||||
@@ -35,14 +35,7 @@ app.MapDefaultEndpoints();
|
|||||||
using (var scope = app.Services.CreateScope())
|
using (var scope = app.Services.CreateScope())
|
||||||
{
|
{
|
||||||
var db = scope.ServiceProvider.GetRequiredService<AppDatabase>();
|
var db = scope.ServiceProvider.GetRequiredService<AppDatabase>();
|
||||||
try
|
await db.Database.MigrateAsync();
|
||||||
{
|
|
||||||
await db.Database.MigrateAsync();
|
|
||||||
}
|
|
||||||
catch (Exception err)
|
|
||||||
{
|
|
||||||
Console.WriteLine(err);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Configure application middleware pipeline
|
// Configure application middleware pipeline
|
||||||
@@ -53,4 +46,4 @@ app.ConfigureGrpcServices();
|
|||||||
|
|
||||||
app.UseSwaggerManifest("DysonNetwork.Pass");
|
app.UseSwaggerManifest("DysonNetwork.Pass");
|
||||||
|
|
||||||
app.Run();
|
app.Run();
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using DysonNetwork.Pass.Credit;
|
||||||
using DysonNetwork.Pass.Handlers;
|
using DysonNetwork.Pass.Handlers;
|
||||||
using DysonNetwork.Pass.Wallet;
|
using DysonNetwork.Pass.Wallet;
|
||||||
using Quartz;
|
using Quartz;
|
||||||
@@ -73,6 +74,13 @@ public static class ScheduledJobsConfiguration
|
|||||||
.ForJob(lotteryDrawJob)
|
.ForJob(lotteryDrawJob)
|
||||||
.WithIdentity("LotteryDrawTrigger")
|
.WithIdentity("LotteryDrawTrigger")
|
||||||
.WithCronSchedule("0 0 0 * * ?"));
|
.WithCronSchedule("0 0 0 * * ?"));
|
||||||
|
|
||||||
|
var socialCreditValidationJob = new JobKey("SocialCreditValidation");
|
||||||
|
q.AddJob<SocialCreditValidationJob>(opts => opts.WithIdentity(socialCreditValidationJob));
|
||||||
|
q.AddTrigger(opts => opts
|
||||||
|
.ForJob(socialCreditValidationJob)
|
||||||
|
.WithIdentity("SocialCreditValidationTrigger")
|
||||||
|
.WithCronSchedule("0 0 0 * * ?"));
|
||||||
});
|
});
|
||||||
services.AddQuartzHostedService(q => q.WaitForJobsToComplete = true);
|
services.AddQuartzHostedService(q => q.WaitForJobsToComplete = true);
|
||||||
|
|
||||||
|
|||||||
@@ -4,12 +4,19 @@ using NodaTime.Serialization.Protobuf;
|
|||||||
|
|
||||||
namespace DysonNetwork.Shared.Models;
|
namespace DysonNetwork.Shared.Models;
|
||||||
|
|
||||||
|
public enum SocialCreditRecordStatus
|
||||||
|
{
|
||||||
|
Active,
|
||||||
|
Expired
|
||||||
|
}
|
||||||
|
|
||||||
public class SnSocialCreditRecord : ModelBase
|
public class SnSocialCreditRecord : ModelBase
|
||||||
{
|
{
|
||||||
public Guid Id { get; set; }
|
public Guid Id { get; set; }
|
||||||
[MaxLength(1024)] public string ReasonType { get; set; } = string.Empty;
|
[MaxLength(1024)] public string ReasonType { get; set; } = string.Empty;
|
||||||
[MaxLength(1024)] public string Reason { get; set; } = string.Empty;
|
[MaxLength(1024)] public string Reason { get; set; } = string.Empty;
|
||||||
public double Delta { get; set; }
|
public double Delta { get; set; }
|
||||||
|
public SocialCreditRecordStatus Status { get; set; } = SocialCreditRecordStatus.Active;
|
||||||
public Instant? ExpiredAt { get; set; }
|
public Instant? ExpiredAt { get; set; }
|
||||||
|
|
||||||
public Guid AccountId { get; set; }
|
public Guid AccountId { get; set; }
|
||||||
|
|||||||
Reference in New Issue
Block a user