✨ Social credit, leveling service
This commit is contained in:
@@ -2,6 +2,8 @@ using System.Linq.Expressions;
|
|||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using DysonNetwork.Pass.Account;
|
using DysonNetwork.Pass.Account;
|
||||||
using DysonNetwork.Pass.Auth;
|
using DysonNetwork.Pass.Auth;
|
||||||
|
using DysonNetwork.Pass.Credit;
|
||||||
|
using DysonNetwork.Pass.Leveling;
|
||||||
using DysonNetwork.Pass.Permission;
|
using DysonNetwork.Pass.Permission;
|
||||||
using DysonNetwork.Pass.Wallet;
|
using DysonNetwork.Pass.Wallet;
|
||||||
using DysonNetwork.Shared.Data;
|
using DysonNetwork.Shared.Data;
|
||||||
@@ -48,6 +50,9 @@ public class AppDatabase(
|
|||||||
public DbSet<Coupon> WalletCoupons { get; set; } = null!;
|
public DbSet<Coupon> WalletCoupons { get; set; } = null!;
|
||||||
|
|
||||||
public DbSet<Punishment> Punishments { get; set; } = null!;
|
public DbSet<Punishment> Punishments { get; set; } = null!;
|
||||||
|
|
||||||
|
public DbSet<SocialCreditRecord> SocialCreditRecords { get; set; } = null!;
|
||||||
|
public DbSet<ExperienceRecord> ExperienceRecords { get; set; } = null!;
|
||||||
|
|
||||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
||||||
{
|
{
|
||||||
|
17
DysonNetwork.Pass/Credit/SocialCreditRecord.cs
Normal file
17
DysonNetwork.Pass/Credit/SocialCreditRecord.cs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using DysonNetwork.Shared.Data;
|
||||||
|
using NodaTime;
|
||||||
|
|
||||||
|
namespace DysonNetwork.Pass.Credit;
|
||||||
|
|
||||||
|
public class SocialCreditRecord : ModelBase
|
||||||
|
{
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
[MaxLength(1024)] public string ReasonType { get; set; } = string.Empty;
|
||||||
|
[MaxLength(1024)] public string Reason { get; set; } = string.Empty;
|
||||||
|
public double Delta { get; set; }
|
||||||
|
public Instant? ExpiredAt { get; set; }
|
||||||
|
|
||||||
|
public Guid AccountId { get; set; }
|
||||||
|
public Account.Account Account { get; set; } = null!;
|
||||||
|
}
|
38
DysonNetwork.Pass/Credit/SocialCreditService.cs
Normal file
38
DysonNetwork.Pass/Credit/SocialCreditService.cs
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
using DysonNetwork.Shared.Cache;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace DysonNetwork.Pass.Credit;
|
||||||
|
|
||||||
|
public class SocialCreditService(AppDatabase db, ICacheService cache)
|
||||||
|
{
|
||||||
|
private const string CacheKeyPrefix = "account:credits:";
|
||||||
|
|
||||||
|
public async Task<SocialCreditRecord> AddRecord(string reasonType, string reason, double delta, Guid accountId)
|
||||||
|
{
|
||||||
|
var record = new SocialCreditRecord
|
||||||
|
{
|
||||||
|
ReasonType = reasonType,
|
||||||
|
Reason = reason,
|
||||||
|
Delta = delta,
|
||||||
|
AccountId = accountId,
|
||||||
|
};
|
||||||
|
db.SocialCreditRecords.Add(record);
|
||||||
|
await db.SaveChangesAsync();
|
||||||
|
return record;
|
||||||
|
}
|
||||||
|
|
||||||
|
private const double BaseSocialCredit = 100;
|
||||||
|
|
||||||
|
public async Task<double> GetSocialCredit(Guid accountId)
|
||||||
|
{
|
||||||
|
var cached = await cache.GetAsync<double?>($"{CacheKeyPrefix}{accountId}");
|
||||||
|
if (cached.HasValue) return cached.Value;
|
||||||
|
|
||||||
|
var records = await db.SocialCreditRecords
|
||||||
|
.Where(x => x.AccountId == accountId)
|
||||||
|
.SumAsync(x => x.Delta);
|
||||||
|
records += BaseSocialCredit;
|
||||||
|
await cache.SetAsync($"{CacheKeyPrefix}{accountId}", records);
|
||||||
|
return records;
|
||||||
|
}
|
||||||
|
}
|
16
DysonNetwork.Pass/Leveling/ExperienceRecord.cs
Normal file
16
DysonNetwork.Pass/Leveling/ExperienceRecord.cs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using DysonNetwork.Shared.Data;
|
||||||
|
|
||||||
|
namespace DysonNetwork.Pass.Leveling;
|
||||||
|
|
||||||
|
public class ExperienceRecord : ModelBase
|
||||||
|
{
|
||||||
|
public Guid Id { get; set; } = Guid.NewGuid();
|
||||||
|
[MaxLength(1024)] public string ReasonType { get; set; } = string.Empty;
|
||||||
|
[MaxLength(1024)] public string Reason { get; set; } = string.Empty;
|
||||||
|
public long Delta { get; set; }
|
||||||
|
public double BonusMultiplier { get; set; }
|
||||||
|
|
||||||
|
public Guid AccountId { get; set; }
|
||||||
|
public Account.Account Account { get; set; } = null!;
|
||||||
|
}
|
42
DysonNetwork.Pass/Leveling/ExperienceService.cs
Normal file
42
DysonNetwork.Pass/Leveling/ExperienceService.cs
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
using DysonNetwork.Pass.Wallet;
|
||||||
|
using DysonNetwork.Shared.Cache;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace DysonNetwork.Pass.Leveling;
|
||||||
|
|
||||||
|
public class ExperienceService(AppDatabase db, SubscriptionService subscriptions, ICacheService cache)
|
||||||
|
{
|
||||||
|
public async Task<ExperienceRecord> AddRecord(string reasonType, string reason, long delta, Guid accountId)
|
||||||
|
{
|
||||||
|
var record = new ExperienceRecord
|
||||||
|
{
|
||||||
|
ReasonType = reasonType,
|
||||||
|
Reason = reason,
|
||||||
|
Delta = delta,
|
||||||
|
AccountId = accountId,
|
||||||
|
};
|
||||||
|
|
||||||
|
var perkSubscription = await subscriptions.GetPerkSubscriptionAsync(accountId);
|
||||||
|
if (perkSubscription is not null)
|
||||||
|
{
|
||||||
|
record.BonusMultiplier = perkSubscription.Identifier switch
|
||||||
|
{
|
||||||
|
SubscriptionType.Stellar => 1.5,
|
||||||
|
SubscriptionType.Nova => 2,
|
||||||
|
SubscriptionType.Supernova => 2,
|
||||||
|
_ => 1
|
||||||
|
};
|
||||||
|
if (record.Delta >= 0)
|
||||||
|
record.Delta = (long)Math.Floor(record.Delta * record.BonusMultiplier);
|
||||||
|
}
|
||||||
|
|
||||||
|
db.ExperienceRecords.Add(record);
|
||||||
|
await db.SaveChangesAsync();
|
||||||
|
|
||||||
|
await db.AccountProfiles
|
||||||
|
.Where(p => p.AccountId == accountId)
|
||||||
|
.ExecuteUpdateAsync(p => p.SetProperty(v => v.Experience, v => v.Experience + record.Delta));
|
||||||
|
|
||||||
|
return record;
|
||||||
|
}
|
||||||
|
}
|
2008
DysonNetwork.Pass/Migrations/20250820120632_AddCreditAndLevelingRecords.Designer.cs
generated
Normal file
2008
DysonNetwork.Pass/Migrations/20250820120632_AddCreditAndLevelingRecords.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,85 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using NodaTime;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace DysonNetwork.Pass.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddCreditAndLevelingRecords : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "experience_records",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
reason_type = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false),
|
||||||
|
reason = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false),
|
||||||
|
delta = table.Column<long>(type: "bigint", nullable: false),
|
||||||
|
account_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||||
|
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||||
|
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("pk_experience_records", x => x.id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "fk_experience_records_accounts_account_id",
|
||||||
|
column: x => x.account_id,
|
||||||
|
principalTable: "accounts",
|
||||||
|
principalColumn: "id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "social_credit_records",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
reason_type = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false),
|
||||||
|
reason = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false),
|
||||||
|
delta = table.Column<double>(type: "double precision", nullable: false),
|
||||||
|
expired_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
||||||
|
account_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||||
|
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||||
|
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("pk_social_credit_records", x => x.id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "fk_social_credit_records_accounts_account_id",
|
||||||
|
column: x => x.account_id,
|
||||||
|
principalTable: "accounts",
|
||||||
|
principalColumn: "id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "ix_experience_records_account_id",
|
||||||
|
table: "experience_records",
|
||||||
|
column: "account_id");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "ix_social_credit_records_account_id",
|
||||||
|
table: "social_credit_records",
|
||||||
|
column: "account_id");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "experience_records");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "social_credit_records");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
2012
DysonNetwork.Pass/Migrations/20250820170714_AddLevelingBonusMultiplier.Designer.cs
generated
Normal file
2012
DysonNetwork.Pass/Migrations/20250820170714_AddLevelingBonusMultiplier.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 AddLevelingBonusMultiplier : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<int>(
|
||||||
|
name: "bonus_multiplier",
|
||||||
|
table: "experience_records",
|
||||||
|
type: "integer",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "bonus_multiplier",
|
||||||
|
table: "experience_records");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1042,6 +1042,110 @@ namespace DysonNetwork.Pass.Migrations
|
|||||||
b.ToTable("auth_sessions", (string)null);
|
b.ToTable("auth_sessions", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DysonNetwork.Pass.Credit.SocialCreditRecord", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<Guid>("AccountId")
|
||||||
|
.HasColumnType("uuid")
|
||||||
|
.HasColumnName("account_id");
|
||||||
|
|
||||||
|
b.Property<Instant>("CreatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("created_at");
|
||||||
|
|
||||||
|
b.Property<Instant?>("DeletedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("deleted_at");
|
||||||
|
|
||||||
|
b.Property<double>("Delta")
|
||||||
|
.HasColumnType("double precision")
|
||||||
|
.HasColumnName("delta");
|
||||||
|
|
||||||
|
b.Property<Instant?>("ExpiredAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("expired_at");
|
||||||
|
|
||||||
|
b.Property<string>("Reason")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(1024)
|
||||||
|
.HasColumnType("character varying(1024)")
|
||||||
|
.HasColumnName("reason");
|
||||||
|
|
||||||
|
b.Property<string>("ReasonType")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(1024)
|
||||||
|
.HasColumnType("character varying(1024)")
|
||||||
|
.HasColumnName("reason_type");
|
||||||
|
|
||||||
|
b.Property<Instant>("UpdatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("updated_at");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("pk_social_credit_records");
|
||||||
|
|
||||||
|
b.HasIndex("AccountId")
|
||||||
|
.HasDatabaseName("ix_social_credit_records_account_id");
|
||||||
|
|
||||||
|
b.ToTable("social_credit_records", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DysonNetwork.Pass.Leveling.ExperienceRecord", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<Guid>("AccountId")
|
||||||
|
.HasColumnType("uuid")
|
||||||
|
.HasColumnName("account_id");
|
||||||
|
|
||||||
|
b.Property<int>("BonusMultiplier")
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasColumnName("bonus_multiplier");
|
||||||
|
|
||||||
|
b.Property<Instant>("CreatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("created_at");
|
||||||
|
|
||||||
|
b.Property<Instant?>("DeletedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("deleted_at");
|
||||||
|
|
||||||
|
b.Property<long>("Delta")
|
||||||
|
.HasColumnType("bigint")
|
||||||
|
.HasColumnName("delta");
|
||||||
|
|
||||||
|
b.Property<string>("Reason")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(1024)
|
||||||
|
.HasColumnType("character varying(1024)")
|
||||||
|
.HasColumnName("reason");
|
||||||
|
|
||||||
|
b.Property<string>("ReasonType")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(1024)
|
||||||
|
.HasColumnType("character varying(1024)")
|
||||||
|
.HasColumnName("reason_type");
|
||||||
|
|
||||||
|
b.Property<Instant>("UpdatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("updated_at");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("pk_experience_records");
|
||||||
|
|
||||||
|
b.HasIndex("AccountId")
|
||||||
|
.HasDatabaseName("ix_experience_records_account_id");
|
||||||
|
|
||||||
|
b.ToTable("experience_records", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("DysonNetwork.Pass.Permission.PermissionGroup", b =>
|
modelBuilder.Entity("DysonNetwork.Pass.Permission.PermissionGroup", b =>
|
||||||
{
|
{
|
||||||
b.Property<Guid>("Id")
|
b.Property<Guid>("Id")
|
||||||
@@ -1743,6 +1847,30 @@ namespace DysonNetwork.Pass.Migrations
|
|||||||
b.Navigation("Challenge");
|
b.Navigation("Challenge");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DysonNetwork.Pass.Credit.SocialCreditRecord", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("DysonNetwork.Pass.Account.Account", "Account")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("AccountId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired()
|
||||||
|
.HasConstraintName("fk_social_credit_records_accounts_account_id");
|
||||||
|
|
||||||
|
b.Navigation("Account");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DysonNetwork.Pass.Leveling.ExperienceRecord", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("DysonNetwork.Pass.Account.Account", "Account")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("AccountId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired()
|
||||||
|
.HasConstraintName("fk_experience_records_accounts_account_id");
|
||||||
|
|
||||||
|
b.Navigation("Account");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("DysonNetwork.Pass.Permission.PermissionGroupMember", b =>
|
modelBuilder.Entity("DysonNetwork.Pass.Permission.PermissionGroupMember", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("DysonNetwork.Pass.Permission.PermissionGroup", "Group")
|
b.HasOne("DysonNetwork.Pass.Permission.PermissionGroup", "Group")
|
||||||
|
@@ -15,7 +15,9 @@ using System.Text.Json;
|
|||||||
using System.Threading.RateLimiting;
|
using System.Threading.RateLimiting;
|
||||||
using DysonNetwork.Pass.Auth.OidcProvider.Options;
|
using DysonNetwork.Pass.Auth.OidcProvider.Options;
|
||||||
using DysonNetwork.Pass.Auth.OidcProvider.Services;
|
using DysonNetwork.Pass.Auth.OidcProvider.Services;
|
||||||
|
using DysonNetwork.Pass.Credit;
|
||||||
using DysonNetwork.Pass.Handlers;
|
using DysonNetwork.Pass.Handlers;
|
||||||
|
using DysonNetwork.Pass.Leveling;
|
||||||
using DysonNetwork.Pass.Safety;
|
using DysonNetwork.Pass.Safety;
|
||||||
using DysonNetwork.Pass.Wallet.PaymentHandlers;
|
using DysonNetwork.Pass.Wallet.PaymentHandlers;
|
||||||
using DysonNetwork.Shared.Cache;
|
using DysonNetwork.Shared.Cache;
|
||||||
@@ -203,6 +205,8 @@ public static class ServiceCollectionExtensions
|
|||||||
services.AddScoped<PaymentService>();
|
services.AddScoped<PaymentService>();
|
||||||
services.AddScoped<AfdianPaymentHandler>();
|
services.AddScoped<AfdianPaymentHandler>();
|
||||||
services.AddScoped<SafetyService>();
|
services.AddScoped<SafetyService>();
|
||||||
|
services.AddScoped<SocialCreditService>();
|
||||||
|
services.AddScoped<ExperienceService>();
|
||||||
|
|
||||||
services.Configure<OidcProviderOptions>(configuration.GetSection("OidcProvider"));
|
services.Configure<OidcProviderOptions>(configuration.GetSection("OidcProvider"));
|
||||||
services.AddScoped<OidcProviderService>();
|
services.AddScoped<OidcProviderService>();
|
||||||
|
Reference in New Issue
Block a user