♻️ Centralized data models (wip)

This commit is contained in:
2025-09-27 14:09:28 +08:00
parent 51b6f7309e
commit e70d8371f8
206 changed files with 1352 additions and 2128 deletions

View File

@@ -1,31 +0,0 @@
using System.ComponentModel.DataAnnotations;
using DysonNetwork.Shared.Data;
using NodaTime;
namespace DysonNetwork.Pass.Account;
public enum AbuseReportType
{
Copyright,
Harassment,
Impersonation,
OffensiveContent,
Spam,
PrivacyViolation,
IllegalContent,
Other
}
public class AbuseReport : ModelBase
{
public Guid Id { get; set; } = Guid.NewGuid();
[MaxLength(4096)] public string ResourceIdentifier { get; set; } = null!;
public AbuseReportType Type { get; set; }
[MaxLength(8192)] public string Reason { get; set; } = null!;
public Instant? ResolvedAt { get; set; }
[MaxLength(8192)] public string? Resolution { get; set; }
public Guid AccountId { get; set; }
public Account Account { get; set; } = null!;
}

View File

@@ -1,405 +0,0 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Text.Json.Serialization;
using DysonNetwork.Pass.Wallet;
using DysonNetwork.Shared.Data;
using Microsoft.EntityFrameworkCore;
using NodaTime;
using NodaTime.Serialization.Protobuf;
using OtpNet;
using VerificationMark = DysonNetwork.Shared.Data.VerificationMark;
namespace DysonNetwork.Pass.Account;
[Index(nameof(Name), IsUnique = true)]
public class Account : ModelBase
{
public Guid Id { get; set; } = Guid.NewGuid();
[MaxLength(256)] public string Name { get; set; } = string.Empty;
[MaxLength(256)] public string Nick { get; set; } = string.Empty;
[MaxLength(32)] public string Language { get; set; } = string.Empty;
[MaxLength(32)] public string Region { get; set; } = string.Empty;
public Instant? ActivatedAt { get; set; }
public bool IsSuperuser { get; set; } = false;
// The ID is the BotAccount ID in the DysonNetwork.Develop
public Guid? AutomatedId { get; set; }
public AccountProfile Profile { get; set; } = null!;
public ICollection<AccountContact> Contacts { get; set; } = new List<AccountContact>();
public ICollection<AccountBadge> Badges { get; set; } = new List<AccountBadge>();
[JsonIgnore] public ICollection<AccountAuthFactor> AuthFactors { get; set; } = new List<AccountAuthFactor>();
[JsonIgnore] public ICollection<AccountConnection> Connections { get; set; } = new List<AccountConnection>();
[JsonIgnore] public ICollection<Auth.AuthSession> Sessions { get; set; } = new List<Auth.AuthSession>();
[JsonIgnore] public ICollection<Auth.AuthChallenge> Challenges { get; set; } = new List<Auth.AuthChallenge>();
[JsonIgnore] public ICollection<Relationship> OutgoingRelationships { get; set; } = new List<Relationship>();
[JsonIgnore] public ICollection<Relationship> IncomingRelationships { get; set; } = new List<Relationship>();
[NotMapped] public SubscriptionReferenceObject? PerkSubscription { get; set; }
public Shared.Proto.Account ToProtoValue()
{
var proto = new Shared.Proto.Account
{
Id = Id.ToString(),
Name = Name,
Nick = Nick,
Language = Language,
Region = Region,
ActivatedAt = ActivatedAt?.ToTimestamp(),
IsSuperuser = IsSuperuser,
Profile = Profile.ToProtoValue(),
PerkSubscription = PerkSubscription?.ToProtoValue(),
CreatedAt = CreatedAt.ToTimestamp(),
UpdatedAt = UpdatedAt.ToTimestamp(),
AutomatedId = AutomatedId?.ToString()
};
// Add contacts
foreach (var contact in Contacts)
proto.Contacts.Add(contact.ToProtoValue());
// Add badges
foreach (var badge in Badges)
proto.Badges.Add(badge.ToProtoValue());
return proto;
}
public static Account FromProtoValue(Shared.Proto.Account proto)
{
var account = new Account
{
Id = Guid.Parse(proto.Id),
Name = proto.Name,
Nick = proto.Nick,
Language = proto.Language,
Region = proto.Region,
ActivatedAt = proto.ActivatedAt?.ToInstant(),
IsSuperuser = proto.IsSuperuser,
PerkSubscription = proto.PerkSubscription is not null
? SubscriptionReferenceObject.FromProtoValue(proto.PerkSubscription)
: null,
CreatedAt = proto.CreatedAt.ToInstant(),
UpdatedAt = proto.UpdatedAt.ToInstant(),
AutomatedId = proto.AutomatedId is not null ? Guid.Parse(proto.AutomatedId) : null,
Profile = AccountProfile.FromProtoValue(proto.Profile)
};
foreach (var contactProto in proto.Contacts)
account.Contacts.Add(AccountContact.FromProtoValue(contactProto));
foreach (var badgeProto in proto.Badges)
account.Badges.Add(AccountBadge.FromProtoValue(badgeProto));
return account;
}
}
public abstract class Leveling
{
private const int MaxLevel = 120;
private const double BaseExp = 100.0;
private const double K = 3.52; // tweak this for balance
// Single level XP requirement (from L → L+1)
public static double ExpForLevel(int level)
{
if (level < 1) return 0;
return BaseExp + K * Math.Pow(level - 1, 2);
}
// Total cumulative XP required to reach level L
public static double TotalExpForLevel(int level)
{
if (level < 1) return 0;
return BaseExp * level + K * ((level - 1) * level * (2 * level - 1)) / 6.0;
}
// Get level from experience
public static int GetLevelFromExp(int xp)
{
if (xp < 0) return 0;
int level = 0;
while (level < MaxLevel && TotalExpForLevel(level + 1) <= xp)
{
level++;
}
return level;
}
// Progress to next level (0.0 ~ 1.0)
public static double GetProgressToNextLevel(int xp)
{
int currentLevel = GetLevelFromExp(xp);
if (currentLevel >= MaxLevel) return 1.0;
double prevTotal = TotalExpForLevel(currentLevel);
double nextTotal = TotalExpForLevel(currentLevel + 1);
return (xp - prevTotal) / (nextTotal - prevTotal);
}
}
public class AccountProfile : ModelBase, IIdentifiedResource
{
public Guid Id { get; set; } = Guid.NewGuid();
[MaxLength(256)] public string? FirstName { get; set; }
[MaxLength(256)] public string? MiddleName { get; set; }
[MaxLength(256)] public string? LastName { get; set; }
[MaxLength(4096)] public string? Bio { get; set; }
[MaxLength(1024)] public string? Gender { get; set; }
[MaxLength(1024)] public string? Pronouns { get; set; }
[MaxLength(1024)] public string? TimeZone { get; set; }
[MaxLength(1024)] public string? Location { get; set; }
[Column(TypeName = "jsonb")] public List<ProfileLink>? Links { get; set; }
public Instant? Birthday { get; set; }
public Instant? LastSeenAt { get; set; }
[Column(TypeName = "jsonb")] public VerificationMark? Verification { get; set; }
[Column(TypeName = "jsonb")] public BadgeReferenceObject? ActiveBadge { get; set; }
public int Experience { get; set; }
[NotMapped]
public int Level => Leveling.GetLevelFromExp(Experience);
[NotMapped]
public double LevelingProgress => Leveling.GetProgressToNextLevel(Experience);
public double SocialCredits { get; set; } = 100;
[NotMapped]
public int SocialCreditsLevel => SocialCredits switch
{
< 100 => -1,
> 100 and < 200 => 0,
< 200 => 1,
_ => 2
};
[Column(TypeName = "jsonb")] public CloudFileReferenceObject? Picture { get; set; }
[Column(TypeName = "jsonb")] public CloudFileReferenceObject? Background { get; set; }
public Guid AccountId { get; set; }
[JsonIgnore] public Account Account { get; set; } = null!;
public Shared.Proto.AccountProfile ToProtoValue()
{
var proto = new Shared.Proto.AccountProfile
{
Id = Id.ToString(),
FirstName = FirstName ?? string.Empty,
MiddleName = MiddleName ?? string.Empty,
LastName = LastName ?? string.Empty,
Bio = Bio ?? string.Empty,
Gender = Gender ?? string.Empty,
Pronouns = Pronouns ?? string.Empty,
TimeZone = TimeZone ?? string.Empty,
Location = Location ?? string.Empty,
Birthday = Birthday?.ToTimestamp(),
LastSeenAt = LastSeenAt?.ToTimestamp(),
Experience = Experience,
Level = Level,
LevelingProgress = LevelingProgress,
SocialCredits = SocialCredits,
SocialCreditsLevel = SocialCreditsLevel,
Picture = Picture?.ToProtoValue(),
Background = Background?.ToProtoValue(),
AccountId = AccountId.ToString(),
Verification = Verification?.ToProtoValue(),
ActiveBadge = ActiveBadge?.ToProtoValue(),
CreatedAt = CreatedAt.ToTimestamp(),
UpdatedAt = UpdatedAt.ToTimestamp()
};
return proto;
}
public static AccountProfile FromProtoValue(Shared.Proto.AccountProfile proto)
{
var profile = new AccountProfile
{
Id = Guid.Parse(proto.Id),
FirstName = proto.FirstName,
LastName = proto.LastName,
MiddleName = proto.MiddleName,
Bio = proto.Bio,
Gender = proto.Gender,
Pronouns = proto.Pronouns,
TimeZone = proto.TimeZone,
Location = proto.Location,
Birthday = proto.Birthday?.ToInstant(),
LastSeenAt = proto.LastSeenAt?.ToInstant(),
Verification = proto.Verification is null ? null : VerificationMark.FromProtoValue(proto.Verification),
ActiveBadge = proto.ActiveBadge is null ? null : BadgeReferenceObject.FromProtoValue(proto.ActiveBadge),
Experience = proto.Experience,
SocialCredits = proto.SocialCredits,
Picture = proto.Picture is null ? null : CloudFileReferenceObject.FromProtoValue(proto.Picture),
Background = proto.Background is null ? null : CloudFileReferenceObject.FromProtoValue(proto.Background),
AccountId = Guid.Parse(proto.AccountId),
CreatedAt = proto.CreatedAt.ToInstant(),
UpdatedAt = proto.UpdatedAt.ToInstant()
};
return profile;
}
public string ResourceIdentifier => $"account:profile:{Id}";
}
public class ProfileLink
{
public string Name { get; set; } = string.Empty;
public string Url { get; set; } = string.Empty;
}
public class AccountContact : ModelBase
{
public Guid Id { get; set; }
public AccountContactType Type { get; set; }
public Instant? VerifiedAt { get; set; }
public bool IsPrimary { get; set; } = false;
public bool IsPublic { get; set; } = false;
[MaxLength(1024)] public string Content { get; set; } = string.Empty;
public Guid AccountId { get; set; }
[JsonIgnore] public Account Account { get; set; } = null!;
public Shared.Proto.AccountContact ToProtoValue()
{
var proto = new Shared.Proto.AccountContact
{
Id = Id.ToString(),
Type = Type switch
{
AccountContactType.Email => Shared.Proto.AccountContactType.Email,
AccountContactType.PhoneNumber => Shared.Proto.AccountContactType.PhoneNumber,
AccountContactType.Address => Shared.Proto.AccountContactType.Address,
_ => Shared.Proto.AccountContactType.Unspecified
},
Content = Content,
IsPrimary = IsPrimary,
VerifiedAt = VerifiedAt?.ToTimestamp(),
AccountId = AccountId.ToString(),
CreatedAt = CreatedAt.ToTimestamp(),
UpdatedAt = UpdatedAt.ToTimestamp()
};
return proto;
}
public static AccountContact FromProtoValue(Shared.Proto.AccountContact proto)
{
var contact = new AccountContact
{
Id = Guid.Parse(proto.Id),
AccountId = Guid.Parse(proto.AccountId),
Type = proto.Type switch
{
Shared.Proto.AccountContactType.Email => AccountContactType.Email,
Shared.Proto.AccountContactType.PhoneNumber => AccountContactType.PhoneNumber,
Shared.Proto.AccountContactType.Address => AccountContactType.Address,
_ => AccountContactType.Email
},
Content = proto.Content,
IsPrimary = proto.IsPrimary,
VerifiedAt = proto.VerifiedAt?.ToInstant(),
CreatedAt = proto.CreatedAt.ToInstant(),
UpdatedAt = proto.UpdatedAt.ToInstant()
};
return contact;
}
}
public enum AccountContactType
{
Email,
PhoneNumber,
Address
}
public class AccountAuthFactor : ModelBase
{
public Guid Id { get; set; }
public AccountAuthFactorType Type { get; set; }
[JsonIgnore][MaxLength(8196)] public string? Secret { get; set; }
[JsonIgnore]
[Column(TypeName = "jsonb")]
public Dictionary<string, object>? Config { get; set; } = new();
/// <summary>
/// The trustworthy stands for how safe is this auth factor.
/// Basically, it affects how many steps it can complete in authentication.
/// Besides, users may need to use some high-trustworthy level auth factors when confirming some dangerous operations.
/// </summary>
public int Trustworthy { get; set; } = 1;
public Instant? EnabledAt { get; set; }
public Instant? ExpiredAt { get; set; }
public Guid AccountId { get; set; }
[JsonIgnore] public Account Account { get; set; } = null!;
public AccountAuthFactor HashSecret(int cost = 12)
{
if (Secret == null) return this;
Secret = BCrypt.Net.BCrypt.HashPassword(Secret, workFactor: cost);
return this;
}
public bool VerifyPassword(string password)
{
if (Secret == null)
throw new InvalidOperationException("Auth factor with no secret cannot be verified with password.");
switch (Type)
{
case AccountAuthFactorType.Password:
case AccountAuthFactorType.PinCode:
return BCrypt.Net.BCrypt.Verify(password, Secret);
case AccountAuthFactorType.TimedCode:
var otp = new Totp(Base32Encoding.ToBytes(Secret));
return otp.VerifyTotp(DateTime.UtcNow, password, out _, new VerificationWindow(previous: 5, future: 5));
case AccountAuthFactorType.EmailCode:
case AccountAuthFactorType.InAppCode:
default:
throw new InvalidOperationException("Unsupported verification type, use CheckDeliveredCode instead.");
}
}
/// <summary>
/// This dictionary will be returned to the client and should only be set when it just created.
/// Useful for passing the client some data to finishing setup and recovery code.
/// </summary>
[NotMapped]
public Dictionary<string, object>? CreatedResponse { get; set; }
}
public enum AccountAuthFactorType
{
Password,
EmailCode,
InAppCode,
TimedCode,
PinCode,
}
public class AccountConnection : ModelBase
{
public Guid Id { get; set; } = Guid.NewGuid();
[MaxLength(4096)] public string Provider { get; set; } = null!;
[MaxLength(8192)] public string ProvidedIdentifier { get; set; } = null!;
[Column(TypeName = "jsonb")] public Dictionary<string, object>? Meta { get; set; } = new();
[JsonIgnore][MaxLength(4096)] public string? AccessToken { get; set; }
[JsonIgnore][MaxLength(4096)] public string? RefreshToken { get; set; }
public Instant? LastUsedAt { get; set; }
public Guid AccountId { get; set; }
public Account Account { get; set; } = null!;
}

View File

@@ -1,16 +1,15 @@
using System.ComponentModel.DataAnnotations;
using DysonNetwork.Pass.Auth;
using DysonNetwork.Pass.Permission;
using DysonNetwork.Pass.Wallet;
using DysonNetwork.Shared.Data;
using DysonNetwork.Shared.Error;
using DysonNetwork.Shared.Models;
using DysonNetwork.Shared.Proto;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using NodaTime;
using AuthService = DysonNetwork.Pass.Auth.AuthService;
using AuthSession = DysonNetwork.Pass.Auth.AuthSession;
using SnAuthSession = DysonNetwork.Shared.Models.SnAuthSession;
namespace DysonNetwork.Pass.Account;
@@ -132,7 +131,7 @@ public class AccountCurrentController(
Usage = "profile.picture"
}
);
profile.Picture = CloudFileReferenceObject.FromProtoValue(file);
profile.Picture = SnCloudFileReferenceObject.FromProtoValue(file);
}
if (request.BackgroundId is not null)
@@ -150,7 +149,7 @@ public class AccountCurrentController(
Usage = "profile.background"
}
);
profile.Background = CloudFileReferenceObject.FromProtoValue(file);
profile.Background = SnCloudFileReferenceObject.FromProtoValue(file);
}
db.Update(profile);
@@ -558,10 +557,10 @@ public class AccountCurrentController(
[HttpGet("devices")]
[Authorize]
public async Task<ActionResult<List<AuthClientWithChallenge>>> GetDevices()
public async Task<ActionResult<List<SnAuthClientWithChallenge>>> GetDevices()
{
if (HttpContext.Items["CurrentUser"] is not Account currentUser ||
HttpContext.Items["CurrentSession"] is not AuthSession currentSession) return Unauthorized();
HttpContext.Items["CurrentSession"] is not SnAuthSession currentSession) return Unauthorized();
Response.Headers.Append("X-Auth-Session", currentSession.Id.ToString());
@@ -569,7 +568,7 @@ public class AccountCurrentController(
.Where(device => device.AccountId == currentUser.Id)
.ToListAsync();
var challengeDevices = devices.Select(AuthClientWithChallenge.FromClient).ToList();
var challengeDevices = devices.Select(SnAuthClientWithChallenge.FromClient).ToList();
var deviceIds = challengeDevices.Select(x => x.Id).ToList();
var authChallenges = await db.AuthChallenges
@@ -585,13 +584,13 @@ public class AccountCurrentController(
[HttpGet("sessions")]
[Authorize]
public async Task<ActionResult<List<AuthSession>>> GetSessions(
public async Task<ActionResult<List<SnAuthSession>>> GetSessions(
[FromQuery] int take = 20,
[FromQuery] int offset = 0
)
{
if (HttpContext.Items["CurrentUser"] is not Account currentUser ||
HttpContext.Items["CurrentSession"] is not AuthSession currentSession) return Unauthorized();
HttpContext.Items["CurrentSession"] is not SnAuthSession currentSession) return Unauthorized();
var query = db.AuthSessions
.Include(session => session.Account)
@@ -613,7 +612,7 @@ public class AccountCurrentController(
[HttpDelete("sessions/{id:guid}")]
[Authorize]
public async Task<ActionResult<AuthSession>> DeleteSession(Guid id)
public async Task<ActionResult<SnAuthSession>> DeleteSession(Guid id)
{
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized();
@@ -630,7 +629,7 @@ public class AccountCurrentController(
[HttpDelete("devices/{deviceId}")]
[Authorize]
public async Task<ActionResult<AuthSession>> DeleteDevice(string deviceId)
public async Task<ActionResult<SnAuthSession>> DeleteDevice(string deviceId)
{
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized();
@@ -647,10 +646,10 @@ public class AccountCurrentController(
[HttpDelete("sessions/current")]
[Authorize]
public async Task<ActionResult<AuthSession>> DeleteCurrentSession()
public async Task<ActionResult<SnAuthSession>> DeleteCurrentSession()
{
if (HttpContext.Items["CurrentUser"] is not Account currentUser ||
HttpContext.Items["CurrentSession"] is not AuthSession currentSession) return Unauthorized();
HttpContext.Items["CurrentSession"] is not SnAuthSession currentSession) return Unauthorized();
try
{
@@ -665,7 +664,7 @@ public class AccountCurrentController(
[HttpPatch("devices/{deviceId}/label")]
[Authorize]
public async Task<ActionResult<AuthSession>> UpdateDeviceLabel(string deviceId, [FromBody] string label)
public async Task<ActionResult<SnAuthSession>> UpdateDeviceLabel(string deviceId, [FromBody] string label)
{
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized();
@@ -682,10 +681,10 @@ public class AccountCurrentController(
[HttpPatch("devices/current/label")]
[Authorize]
public async Task<ActionResult<AuthSession>> UpdateCurrentDeviceLabel([FromBody] string label)
public async Task<ActionResult<SnAuthSession>> UpdateCurrentDeviceLabel([FromBody] string label)
{
if (HttpContext.Items["CurrentUser"] is not Account currentUser ||
HttpContext.Items["CurrentSession"] is not AuthSession currentSession) return Unauthorized();
HttpContext.Items["CurrentSession"] is not SnAuthSession currentSession) return Unauthorized();
var device = await db.AuthClients.FirstOrDefaultAsync(d => d.Id == currentSession.Challenge.ClientId);
if (device is null) return NotFound();

View File

@@ -1,6 +1,7 @@
using System.Globalization;
using DysonNetwork.Pass.Wallet;
using DysonNetwork.Shared.Cache;
using DysonNetwork.Shared.Models;
using DysonNetwork.Shared.Proto;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Localization;

View File

@@ -1,12 +1,11 @@
using System.Globalization;
using System.Text.Json;
using DysonNetwork.Pass.Auth;
using DysonNetwork.Pass.Auth.OpenId;
using DysonNetwork.Pass.Localization;
using DysonNetwork.Pass.Mailer;
using DysonNetwork.Pass.Permission;
using DysonNetwork.Shared.Cache;
using DysonNetwork.Shared.Data;
using DysonNetwork.Shared.Models;
using DysonNetwork.Shared.Proto;
using DysonNetwork.Shared.Stream;
using EFCore.BulkExtensions;
@@ -141,7 +140,7 @@ public class AccountService(
var defaultGroup = await db.PermissionGroups.FirstOrDefaultAsync(g => g.Key == "default");
if (defaultGroup is not null)
{
db.PermissionGroupMembers.Add(new PermissionGroupMember
db.PermissionGroupMembers.Add(new SnPermissionGroupMember
{
Actor = $"user:{account.Id}",
Group = defaultGroup
@@ -217,7 +216,7 @@ public class AccountService(
Usage = "profile.picture"
}
);
account.Profile.Picture = CloudFileReferenceObject.FromProtoValue(file);
account.Profile.Picture = SnCloudFileReferenceObject.FromProtoValue(file);
}
if (!string.IsNullOrEmpty(backgroundId))
@@ -231,7 +230,7 @@ public class AccountService(
Usage = "profile.background"
}
);
account.Profile.Background = CloudFileReferenceObject.FromProtoValue(file);
account.Profile.Background = SnCloudFileReferenceObject.FromProtoValue(file);
}
db.Accounts.Add(account);
@@ -516,7 +515,7 @@ public class AccountService(
.AnyAsync(s => s.Challenge.ClientId == id);
}
public async Task<AuthClient> UpdateDeviceName(Account account, string deviceId, string label)
public async Task<SnAuthClient> UpdateDeviceName(Account account, string deviceId, string label)
{
var device = await db.AuthClients.FirstOrDefaultAsync(c => c.DeviceId == deviceId && c.AccountId == account.Id
);

View File

@@ -1,46 +0,0 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Text.Json.Serialization;
using DysonNetwork.Shared.Data;
using DysonNetwork.Shared.GeoIp;
using DysonNetwork.Shared.Proto;
using NodaTime.Serialization.Protobuf;
using Point = NetTopologySuite.Geometries.Point;
namespace DysonNetwork.Pass.Account;
public class ActionLog : ModelBase
{
public Guid Id { get; set; } = Guid.NewGuid();
[MaxLength(4096)] public string Action { get; set; } = null!;
[Column(TypeName = "jsonb")] public Dictionary<string, object> Meta { get; set; } = new();
[MaxLength(512)] public string? UserAgent { get; set; }
[MaxLength(128)] public string? IpAddress { get; set; }
[Column(TypeName = "jsonb")] public GeoPoint? Location { get; set; }
public Guid AccountId { get; set; }
public Account Account { get; set; } = null!;
public Guid? SessionId { get; set; }
public Shared.Proto.ActionLog ToProtoValue()
{
var protoLog = new Shared.Proto.ActionLog
{
Id = Id.ToString(),
Action = Action,
UserAgent = UserAgent ?? string.Empty,
IpAddress = IpAddress ?? string.Empty,
Location = Location?.ToString() ?? string.Empty,
AccountId = AccountId.ToString(),
CreatedAt = CreatedAt.ToTimestamp()
};
// Convert Meta dictionary to Struct
protoLog.Meta.Add(GrpcTypeHelper.ConvertToValueMap(Meta));
if (SessionId.HasValue)
protoLog.SessionId = SessionId.Value.ToString();
return protoLog;
}
}

View File

@@ -1,5 +1,6 @@
using DysonNetwork.Shared.Cache;
using DysonNetwork.Shared.GeoIp;
using DysonNetwork.Shared.Models;
namespace DysonNetwork.Pass.Account;
@@ -36,7 +37,7 @@ public class ActionLogService(GeoIpService geo, FlushBufferService fbs)
else
throw new ArgumentException("No user context was found");
if (request.HttpContext.Items["CurrentSession"] is Auth.AuthSession currentSession)
if (request.HttpContext.Items["CurrentSession"] is SnAuthSession currentSession)
log.SessionId = currentSession.Id;
fbs.Enqueue(log);

View File

@@ -1,123 +0,0 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Text.Json.Serialization;
using DysonNetwork.Shared.Data;
using DysonNetwork.Shared.Proto;
using Google.Protobuf.WellKnownTypes;
using NodaTime;
using NodaTime.Serialization.Protobuf;
namespace DysonNetwork.Pass.Account;
public class AccountBadge : ModelBase
{
public Guid Id { get; set; } = Guid.NewGuid();
[MaxLength(1024)] public string Type { get; set; } = null!;
[MaxLength(1024)] public string? Label { get; set; }
[MaxLength(4096)] public string? Caption { get; set; }
[Column(TypeName = "jsonb")] public Dictionary<string, object?> Meta { get; set; } = new();
public Instant? ActivatedAt { get; set; }
public Instant? ExpiredAt { get; set; }
public Guid AccountId { get; set; }
[JsonIgnore] public Account Account { get; set; } = null!;
public BadgeReferenceObject ToReference()
{
return new BadgeReferenceObject
{
Id = Id,
Type = Type,
Label = Label,
Caption = Caption,
Meta = Meta,
ActivatedAt = ActivatedAt,
ExpiredAt = ExpiredAt,
AccountId = AccountId,
};
}
public Shared.Proto.AccountBadge ToProtoValue()
{
var proto = new Shared.Proto.AccountBadge
{
Id = Id.ToString(),
Type = Type,
Label = Label ?? string.Empty,
Caption = Caption ?? string.Empty,
ActivatedAt = ActivatedAt?.ToTimestamp(),
ExpiredAt = ExpiredAt?.ToTimestamp(),
AccountId = AccountId.ToString(),
CreatedAt = CreatedAt.ToTimestamp(),
UpdatedAt = UpdatedAt.ToTimestamp()
};
proto.Meta.Add(GrpcTypeHelper.ConvertToValueMap(Meta));
return proto;
}
public static AccountBadge FromProtoValue(Shared.Proto.AccountBadge proto)
{
var badge = new AccountBadge
{
Id = Guid.Parse(proto.Id),
AccountId = Guid.Parse(proto.AccountId),
Type = proto.Type,
Label = proto.Label,
Caption = proto.Caption,
ActivatedAt = proto.ActivatedAt?.ToInstant(),
ExpiredAt = proto.ExpiredAt?.ToInstant(),
CreatedAt = proto.CreatedAt.ToInstant(),
UpdatedAt = proto.UpdatedAt.ToInstant()
};
return badge;
}
}
public class BadgeReferenceObject : ModelBase
{
public Guid Id { get; set; }
public string Type { get; set; } = null!;
public string? Label { get; set; }
public string? Caption { get; set; }
public Dictionary<string, object?> Meta { get; set; }
public Instant? ActivatedAt { get; set; }
public Instant? ExpiredAt { get; set; }
public Guid AccountId { get; set; }
public Shared.Proto.BadgeReferenceObject ToProtoValue()
{
var proto = new Shared.Proto.BadgeReferenceObject
{
Id = Id.ToString(),
Type = Type,
Label = Label ?? string.Empty,
Caption = Caption ?? string.Empty,
ActivatedAt = ActivatedAt?.ToTimestamp(),
ExpiredAt = ExpiredAt?.ToTimestamp(),
AccountId = AccountId.ToString()
};
proto.Meta.Add(GrpcTypeHelper.ConvertToValueMap(Meta!));
return proto;
}
public static BadgeReferenceObject FromProtoValue(Shared.Proto.BadgeReferenceObject proto)
{
var badge = new BadgeReferenceObject
{
Id = Guid.Parse(proto.Id),
Type = proto.Type,
Label = proto.Label,
Caption = proto.Caption,
Meta = GrpcTypeHelper.ConvertFromValueMap(proto.Meta).ToDictionary(),
ActivatedAt = proto.ActivatedAt?.ToInstant(),
ExpiredAt = proto.ExpiredAt?.ToInstant(),
AccountId = Guid.Parse(proto.AccountId)
};
return badge;
}
}

View File

@@ -1,4 +1,4 @@
using DysonNetwork.Shared.Data;
using DysonNetwork.Shared.Models;
using DysonNetwork.Shared.Proto;
using Grpc.Core;
using Microsoft.EntityFrameworkCore;
@@ -65,7 +65,7 @@ public class BotAccountReceiverGrpc(
Usage = "profile.picture"
}
);
account.Profile.Picture = CloudFileReferenceObject.FromProtoValue(file);
account.Profile.Picture = SnCloudFileReferenceObject.FromProtoValue(file);
}
if (request.BackgroundId is not null)
@@ -83,7 +83,7 @@ public class BotAccountReceiverGrpc(
Usage = "profile.background"
}
);
account.Profile.Background = CloudFileReferenceObject.FromProtoValue(file);
account.Profile.Background = SnCloudFileReferenceObject.FromProtoValue(file);
}
db.Accounts.Update(account);

View File

@@ -1,128 +0,0 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using DysonNetwork.Shared.Data;
using DysonNetwork.Shared.Proto;
using NodaTime;
using NodaTime.Serialization.Protobuf;
namespace DysonNetwork.Pass.Account;
public enum StatusAttitude
{
Positive,
Negative,
Neutral
}
public class Status : ModelBase
{
public Guid Id { get; set; } = Guid.NewGuid();
public StatusAttitude Attitude { get; set; }
[NotMapped] public bool IsOnline { get; set; }
[NotMapped] public bool IsCustomized { get; set; } = true;
public bool IsInvisible { get; set; }
public bool IsNotDisturb { get; set; }
[MaxLength(1024)] public string? Label { get; set; }
[Column(TypeName = "jsonb")] public Dictionary<string, object>? Meta { get; set; }
public Instant? ClearedAt { get; set; }
[MaxLength(4096)] public string? AppIdentifier { get; set; }
/// <summary>
/// Indicates this status is created based on running process or rich presence
/// </summary>
public bool IsAutomated { get; set; }
public Guid AccountId { get; set; }
public Account Account { get; set; } = null!;
public Shared.Proto.AccountStatus ToProtoValue()
{
var proto = new Shared.Proto.AccountStatus
{
Id = Id.ToString(),
Attitude = Attitude switch
{
StatusAttitude.Positive => Shared.Proto.StatusAttitude.Positive,
StatusAttitude.Negative => Shared.Proto.StatusAttitude.Negative,
StatusAttitude.Neutral => Shared.Proto.StatusAttitude.Neutral,
_ => Shared.Proto.StatusAttitude.Unspecified
},
IsOnline = IsOnline,
IsCustomized = IsCustomized,
IsInvisible = IsInvisible,
IsNotDisturb = IsNotDisturb,
Label = Label ?? string.Empty,
Meta = GrpcTypeHelper.ConvertObjectToByteString(Meta),
ClearedAt = ClearedAt?.ToTimestamp(),
AccountId = AccountId.ToString()
};
return proto;
}
public static Status FromProtoValue(Shared.Proto.AccountStatus proto)
{
var status = new Status
{
Id = Guid.Parse(proto.Id),
Attitude = proto.Attitude switch
{
Shared.Proto.StatusAttitude.Positive => StatusAttitude.Positive,
Shared.Proto.StatusAttitude.Negative => StatusAttitude.Negative,
Shared.Proto.StatusAttitude.Neutral => StatusAttitude.Neutral,
_ => StatusAttitude.Neutral
},
IsOnline = proto.IsOnline,
IsCustomized = proto.IsCustomized,
IsInvisible = proto.IsInvisible,
IsNotDisturb = proto.IsNotDisturb,
Label = proto.Label,
Meta = GrpcTypeHelper.ConvertByteStringToObject<Dictionary<string, object>>(proto.Meta),
ClearedAt = proto.ClearedAt?.ToInstant(),
AccountId = Guid.Parse(proto.AccountId)
};
return status;
}
}
public enum CheckInResultLevel
{
Worst,
Worse,
Normal,
Better,
Best,
Special
}
public class CheckInResult : ModelBase
{
public Guid Id { get; set; } = Guid.NewGuid();
public CheckInResultLevel Level { get; set; }
public decimal? RewardPoints { get; set; }
public int? RewardExperience { get; set; }
[Column(TypeName = "jsonb")] public ICollection<FortuneTip> Tips { get; set; } = new List<FortuneTip>();
public Guid AccountId { get; set; }
public Account Account { get; set; } = null!;
public Instant? BackdatedFrom { get; set; }
}
public class FortuneTip
{
public bool IsPositive { get; set; }
public string Title { get; set; } = null!;
public string Content { get; set; } = null!;
}
/// <summary>
/// This method should not be mapped. Used to generate the daily event calendar.
/// </summary>
public class DailyEventResponse
{
public Instant Date { get; set; }
public CheckInResult? CheckInResult { get; set; }
public ICollection<Status> Statuses { get; set; } = new List<Status>();
}

View File

@@ -1,31 +0,0 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Text.Json.Serialization;
using DysonNetwork.Shared.Data;
using Microsoft.EntityFrameworkCore;
using NodaTime;
namespace DysonNetwork.Pass.Account;
public enum MagicSpellType
{
AccountActivation,
AccountDeactivation,
AccountRemoval,
AuthPasswordReset,
ContactVerification,
}
[Index(nameof(Spell), IsUnique = true)]
public class MagicSpell : ModelBase
{
public Guid Id { get; set; } = Guid.NewGuid();
[JsonIgnore] [MaxLength(1024)] public string Spell { get; set; } = null!;
public MagicSpellType Type { get; set; }
public Instant? ExpiresAt { get; set; }
public Instant? AffectedAt { get; set; }
[Column(TypeName = "jsonb")] public Dictionary<string, object> Meta { get; set; } = new();
public Guid? AccountId { get; set; }
public Account? Account { get; set; }
}

View File

@@ -1,28 +0,0 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using DysonNetwork.Shared.Data;
using NodaTime;
namespace DysonNetwork.Pass.Account;
public enum PunishmentType
{
// TODO: impl the permission modification
PermissionModification,
BlockLogin,
DisableAccount,
Strike
}
public class Punishment : ModelBase
{
public Guid Id { get; set; } = Guid.NewGuid();
[MaxLength(8192)] public string Reason { get; set; } = string.Empty;
public Instant? ExpiredAt { get; set; }
public PunishmentType Type { get; set; }
[Column(TypeName = "jsonb")] public List<string>? BlockedPermissions { get; set; }
public Guid AccountId { get; set; }
public Account Account { get; set; } = null!;
}

View File

@@ -1,36 +0,0 @@
using DysonNetwork.Shared.Data;
using DysonNetwork.Shared.Proto;
using NodaTime;
using NodaTime.Serialization.Protobuf;
namespace DysonNetwork.Pass.Account;
public enum RelationshipStatus : short
{
Friends = 100,
Pending = 0,
Blocked = -100
}
public class Relationship : ModelBase
{
public Guid AccountId { get; set; }
public Account Account { get; set; } = null!;
public Guid RelatedId { get; set; }
public Account Related { get; set; } = null!;
public Instant? ExpiredAt { get; set; }
public RelationshipStatus Status { get; set; } = RelationshipStatus.Pending;
public Shared.Proto.Relationship ToProtoValue() => new()
{
AccountId = AccountId.ToString(),
RelatedId = RelatedId.ToString(),
Account = Account.ToProtoValue(),
Related = Related.ToProtoValue(),
Status = (int)Status,
CreatedAt = CreatedAt.ToTimestamp(),
UpdatedAt = UpdatedAt.ToTimestamp()
};
}

View File

@@ -3,12 +3,9 @@ using System.Reflection;
using System.Text.Json;
using System.Text.Json.Serialization;
using DysonNetwork.Pass.Account;
using DysonNetwork.Pass.Auth;
using DysonNetwork.Pass.Credit;
using DysonNetwork.Pass.Leveling;
using DysonNetwork.Pass.Permission;
using DysonNetwork.Pass.Wallet;
using DysonNetwork.Shared.Data;
using DysonNetwork.Shared.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
using Microsoft.EntityFrameworkCore.Query;
@@ -22,36 +19,36 @@ public class AppDatabase(
IConfiguration configuration
) : DbContext(options)
{
public DbSet<PermissionNode> PermissionNodes { get; set; } = null!;
public DbSet<PermissionGroup> PermissionGroups { get; set; } = null!;
public DbSet<PermissionGroupMember> PermissionGroupMembers { get; set; } = null!;
public DbSet<SnPermissionNode> PermissionNodes { get; set; } = null!;
public DbSet<SnPermissionGroup> PermissionGroups { get; set; } = null!;
public DbSet<SnPermissionGroupMember> PermissionGroupMembers { get; set; } = null!;
public DbSet<MagicSpell> MagicSpells { get; set; } = null!;
public DbSet<SnMagicSpell> MagicSpells { get; set; } = null!;
public DbSet<Account.Account> Accounts { get; set; } = null!;
public DbSet<AccountConnection> AccountConnections { get; set; } = null!;
public DbSet<AccountProfile> AccountProfiles { get; set; } = null!;
public DbSet<SnAccountProfile> AccountProfiles { get; set; } = null!;
public DbSet<AccountContact> AccountContacts { get; set; } = null!;
public DbSet<AccountAuthFactor> AccountAuthFactors { get; set; } = null!;
public DbSet<Relationship> AccountRelationships { get; set; } = null!;
public DbSet<Status> AccountStatuses { get; set; } = null!;
public DbSet<CheckInResult> AccountCheckInResults { get; set; } = null!;
public DbSet<AccountBadge> Badges { get; set; } = null!;
public DbSet<SnAccountRelationship> AccountRelationships { get; set; } = null!;
public DbSet<SnAccountStatus> AccountStatuses { get; set; } = null!;
public DbSet<SnCheckInResult> AccountCheckInResults { get; set; } = null!;
public DbSet<SnAccountBadge> Badges { get; set; } = null!;
public DbSet<ActionLog> ActionLogs { get; set; } = null!;
public DbSet<AbuseReport> AbuseReports { get; set; } = null!;
public DbSet<SnAbuseReport> AbuseReports { get; set; } = null!;
public DbSet<AuthSession> AuthSessions { get; set; } = null!;
public DbSet<AuthChallenge> AuthChallenges { get; set; } = null!;
public DbSet<AuthClient> AuthClients { get; set; } = null!;
public DbSet<ApiKey> ApiKeys { get; set; } = null!;
public DbSet<SnAuthSession> AuthSessions { get; set; } = null!;
public DbSet<SnAuthChallenge> AuthChallenges { get; set; } = null!;
public DbSet<SnAuthClient> AuthClients { get; set; } = null!;
public DbSet<SnApiKey> ApiKeys { get; set; } = null!;
public DbSet<Wallet.Wallet> Wallets { get; set; } = null!;
public DbSet<WalletPocket> WalletPockets { get; set; } = null!;
public DbSet<Order> PaymentOrders { get; set; } = null!;
public DbSet<Transaction> PaymentTransactions { get; set; } = null!;
public DbSet<Subscription> WalletSubscriptions { get; set; } = null!;
public DbSet<Shared.Models.SnWallet> Wallets { get; set; } = null!;
public DbSet<SnWalletPocket> WalletPockets { get; set; } = null!;
public DbSet<SnWalletOrder> PaymentOrders { get; set; } = null!;
public DbSet<SnWalletTransaction> PaymentTransactions { get; set; } = null!;
public DbSet<SnSubscription> WalletSubscriptions { get; set; } = null!;
public DbSet<Coupon> WalletCoupons { get; set; } = null!;
public DbSet<Punishment> Punishments { get; set; } = null!;
public DbSet<SnAccountPunishment> Punishments { get; set; } = null!;
public DbSet<SocialCreditRecord> SocialCreditRecords { get; set; } = null!;
public DbSet<ExperienceRecord> ExperienceRecords { get; set; } = null!;
@@ -74,11 +71,11 @@ public class AppDatabase(
optionsBuilder.UseAsyncSeeding(async (context, _, cancellationToken) =>
{
var defaultPermissionGroup = await context.Set<PermissionGroup>()
var defaultPermissionGroup = await context.Set<SnPermissionGroup>()
.FirstOrDefaultAsync(g => g.Key == "default", cancellationToken);
if (defaultPermissionGroup is null)
{
context.Set<PermissionGroup>().Add(new PermissionGroup
context.Set<SnPermissionGroup>().Add(new SnPermissionGroup
{
Key = "default",
Nodes = new List<string>
@@ -111,21 +108,21 @@ public class AppDatabase(
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<PermissionGroupMember>()
modelBuilder.Entity<SnPermissionGroupMember>()
.HasKey(pg => new { pg.GroupId, pg.Actor });
modelBuilder.Entity<PermissionGroupMember>()
modelBuilder.Entity<SnPermissionGroupMember>()
.HasOne(pg => pg.Group)
.WithMany(g => g.Members)
.HasForeignKey(pg => pg.GroupId)
.OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<Relationship>()
modelBuilder.Entity<SnAccountRelationship>()
.HasKey(r => new { FromAccountId = r.AccountId, ToAccountId = r.RelatedId });
modelBuilder.Entity<Relationship>()
modelBuilder.Entity<SnAccountRelationship>()
.HasOne(r => r.Account)
.WithMany(a => a.OutgoingRelationships)
.HasForeignKey(r => r.AccountId);
modelBuilder.Entity<Relationship>()
modelBuilder.Entity<SnAccountRelationship>()
.HasOne(r => r.Related)
.WithMany(a => a.IncomingRelationships)
.HasForeignKey(r => r.RelatedId);

View File

@@ -1,50 +0,0 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Text.Json.Serialization;
using DysonNetwork.Shared.Data;
using NodaTime.Serialization.Protobuf;
namespace DysonNetwork.Pass.Auth;
public class ApiKey : ModelBase
{
public Guid Id { get; set; } = Guid.NewGuid();
[MaxLength(1024)] public string Label { get; set; } = null!;
public Guid AccountId { get; set; }
public Account.Account Account { get; set; } = null!;
public Guid SessionId { get; set; }
public AuthSession Session { get; set; } = null!;
[NotMapped]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? Key { get; set; }
public DysonNetwork.Shared.Proto.ApiKey ToProtoValue()
{
return new DysonNetwork.Shared.Proto.ApiKey
{
Id = Id.ToString(),
Label = Label,
AccountId = AccountId.ToString(),
SessionId = SessionId.ToString(),
Key = Key,
CreatedAt = CreatedAt.ToTimestamp(),
UpdatedAt = UpdatedAt.ToTimestamp()
};
}
public static ApiKey FromProtoValue(DysonNetwork.Shared.Proto.ApiKey proto)
{
return new ApiKey
{
Id = Guid.Parse(proto.Id),
AccountId = Guid.Parse(proto.AccountId),
SessionId = Guid.Parse(proto.SessionId),
Label = proto.Label,
Key = proto.Key,
CreatedAt = proto.CreatedAt.ToInstant(),
UpdatedAt = proto.UpdatedAt.ToInstant()
};
}
}

View File

@@ -11,6 +11,7 @@ using Microsoft.Extensions.Localization;
using AccountAuthFactor = DysonNetwork.Pass.Account.AccountAuthFactor;
using AccountService = DysonNetwork.Pass.Account.AccountService;
using ActionLogService = DysonNetwork.Pass.Account.ActionLogService;
using DysonNetwork.Shared.Models;
namespace DysonNetwork.Pass.Auth;

View File

@@ -3,6 +3,7 @@ using System.Text.Json;
using System.Text.Json.Serialization;
using DysonNetwork.Pass.Account;
using DysonNetwork.Shared.Cache;
using DysonNetwork.Shared.Models;
using Microsoft.EntityFrameworkCore;
using NodaTime;
@@ -76,10 +77,10 @@ public class AuthService(
return totalRequiredSteps;
}
public async Task<AuthSession> CreateSessionForOidcAsync(Account.Account account, Instant time,
public async Task<SnAuthSession> CreateSessionForOidcAsync(Account.Account account, Instant time,
Guid? customAppId = null)
{
var challenge = new AuthChallenge
var challenge = new SnAuthChallenge
{
AccountId = account.Id,
IpAddress = HttpContext.Connection.RemoteIpAddress?.ToString(),
@@ -89,7 +90,7 @@ public class AuthService(
Type = customAppId is not null ? ChallengeType.OAuth : ChallengeType.Oidc
};
var session = new AuthSession
var session = new SnAuthSession
{
AccountId = account.Id,
CreatedAt = time,
@@ -105,7 +106,7 @@ public class AuthService(
return session;
}
public async Task<AuthClient> GetOrCreateDeviceAsync(
public async Task<SnAuthClient> GetOrCreateDeviceAsync(
Guid accountId,
string deviceId,
string? deviceName = null,
@@ -114,7 +115,7 @@ public class AuthService(
{
var device = await db.AuthClients.FirstOrDefaultAsync(d => d.DeviceId == deviceId && d.AccountId == accountId);
if (device is not null) return device;
device = new AuthClient
device = new SnAuthClient
{
Platform = platform,
DeviceId = deviceId,
@@ -181,7 +182,7 @@ public class AuthService(
}
}
public string CreateToken(AuthSession session)
public string CreateToken(SnAuthSession session)
{
// Load the private key for signing
var privateKeyPem = File.ReadAllText(config["AuthToken:PrivateKeyPath"]!);
@@ -199,7 +200,7 @@ public class AuthService(
/// <param name="challenge">Completed challenge</param>
/// <returns>Signed compact token</returns>
/// <exception cref="ArgumentException">If challenge not completed or session already exists</exception>
public async Task<string> CreateSessionAndIssueToken(AuthChallenge challenge)
public async Task<string> CreateSessionAndIssueToken(SnAuthChallenge challenge)
{
if (challenge.StepRemain != 0)
throw new ArgumentException("Challenge not yet completed.");
@@ -210,7 +211,7 @@ public class AuthService(
throw new ArgumentException("Session already exists for this challenge.");
var now = SystemClock.Instance.GetCurrentInstant();
var session = new AuthSession
var session = new SnAuthSession
{
LastGrantedAt = now,
ExpiredAt = now.Plus(Duration.FromDays(7)),
@@ -256,7 +257,7 @@ public class AuthService(
return $"{payloadBase64}.{signatureBase64}";
}
public async Task<bool> ValidateSudoMode(AuthSession session, string? pinCode)
public async Task<bool> ValidateSudoMode(SnAuthSession session, string? pinCode)
{
// Check if the session is already in sudo mode (cached)
var sudoModeKey = $"accounts:{session.Id}:sudo";
@@ -319,7 +320,7 @@ public class AuthService(
return factor.VerifyPassword(pinCode);
}
public async Task<ApiKey?> GetApiKey(Guid id, Guid? accountId = null)
public async Task<SnApiKey?> GetApiKey(Guid id, Guid? accountId = null)
{
var key = await db.ApiKeys
.Include(e => e.Session)
@@ -329,13 +330,13 @@ public class AuthService(
return key;
}
public async Task<ApiKey> CreateApiKey(Guid accountId, string label, Instant? expiredAt = null)
public async Task<SnApiKey> CreateApiKey(Guid accountId, string label, Instant? expiredAt = null)
{
var key = new ApiKey
var key = new SnApiKey
{
AccountId = accountId,
Label = label,
Session = new AuthSession
Session = new SnAuthSession
{
AccountId = accountId,
ExpiredAt = expiredAt
@@ -348,7 +349,7 @@ public class AuthService(
return key;
}
public async Task<string> IssueApiKeyToken(ApiKey key)
public async Task<string> IssueApiKeyToken(SnApiKey key)
{
key.Session.LastGrantedAt = SystemClock.Instance.GetCurrentInstant();
db.Update(key.Session);
@@ -357,14 +358,14 @@ public class AuthService(
return tk;
}
public async Task RevokeApiKeyToken(ApiKey key)
public async Task RevokeApiKeyToken(SnApiKey key)
{
db.Remove(key);
db.Remove(key.Session);
await db.SaveChangesAsync();
}
public async Task<ApiKey> RotateApiKeyToken(ApiKey key)
public async Task<SnApiKey> RotateApiKeyToken(SnApiKey key)
{
await using var transaction = await db.Database.BeginTransactionAsync();
try
@@ -372,7 +373,7 @@ public class AuthService(
var oldSessionId = key.SessionId;
// Create new session
var newSession = new AuthSession
var newSession = new SnAuthSession
{
AccountId = key.AccountId,
ExpiredAt = key.Session?.ExpiredAt

View File

@@ -1,133 +0,0 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Text.Json.Serialization;
using DysonNetwork.Shared.Data;
using DysonNetwork.Shared.GeoIp;
using NodaTime;
using NodaTime.Serialization.Protobuf;
using Point = NetTopologySuite.Geometries.Point;
namespace DysonNetwork.Pass.Auth;
public class AuthSession : ModelBase
{
public Guid Id { get; set; } = Guid.NewGuid();
public Instant? LastGrantedAt { get; set; }
public Instant? ExpiredAt { get; set; }
public Guid AccountId { get; set; }
[JsonIgnore] public Account.Account Account { get; set; } = null!;
// When the challenge is null, indicates the session is for an API Key
public Guid? ChallengeId { get; set; }
public AuthChallenge? Challenge { get; set; } = null!;
// Indicates the session is for an OIDC connection
public Guid? AppId { get; set; }
public Shared.Proto.AuthSession ToProtoValue() => new()
{
Id = Id.ToString(),
LastGrantedAt = LastGrantedAt?.ToTimestamp(),
ExpiredAt = ExpiredAt?.ToTimestamp(),
AccountId = AccountId.ToString(),
Account = Account.ToProtoValue(),
ChallengeId = ChallengeId.ToString(),
Challenge = Challenge?.ToProtoValue(),
AppId = AppId?.ToString()
};
}
public enum ChallengeType
{
Login,
OAuth, // Trying to authorize other platforms
Oidc // Trying to connect other platforms
}
public enum ClientPlatform
{
Unidentified,
Web,
Ios,
Android,
MacOs,
Windows,
Linux
}
public class AuthChallenge : ModelBase
{
public Guid Id { get; set; } = Guid.NewGuid();
public Instant? ExpiredAt { get; set; }
public int StepRemain { get; set; }
public int StepTotal { get; set; }
public int FailedAttempts { get; set; }
public ChallengeType Type { get; set; } = ChallengeType.Login;
[Column(TypeName = "jsonb")] public List<Guid> BlacklistFactors { get; set; } = new();
[Column(TypeName = "jsonb")] public List<string> Audiences { get; set; } = new();
[Column(TypeName = "jsonb")] public List<string> Scopes { get; set; } = new();
[MaxLength(128)] public string? IpAddress { get; set; }
[MaxLength(512)] public string? UserAgent { get; set; }
[MaxLength(1024)] public string? Nonce { get; set; }
[Column(TypeName = "jsonb")] public GeoPoint? Location { get; set; }
public Guid AccountId { get; set; }
[JsonIgnore] public Account.Account Account { get; set; } = null!;
public Guid? ClientId { get; set; }
public AuthClient? Client { get; set; } = null!;
public AuthChallenge Normalize()
{
if (StepRemain == 0 && BlacklistFactors.Count == 0) StepRemain = StepTotal;
return this;
}
public Shared.Proto.AuthChallenge ToProtoValue() => new()
{
Id = Id.ToString(),
ExpiredAt = ExpiredAt?.ToTimestamp(),
StepRemain = StepRemain,
StepTotal = StepTotal,
FailedAttempts = FailedAttempts,
Type = (Shared.Proto.ChallengeType)Type,
BlacklistFactors = { BlacklistFactors.Select(x => x.ToString()) },
Audiences = { Audiences },
Scopes = { Scopes },
IpAddress = IpAddress,
UserAgent = UserAgent,
DeviceId = Client!.DeviceId,
Nonce = Nonce,
AccountId = AccountId.ToString()
};
}
public class AuthClient : ModelBase
{
public Guid Id { get; set; } = Guid.NewGuid();
public ClientPlatform Platform { get; set; } = ClientPlatform.Unidentified;
[MaxLength(1024)] public string DeviceName { get; set; } = string.Empty;
[MaxLength(1024)] public string? DeviceLabel { get; set; }
[MaxLength(1024)] public string DeviceId { get; set; } = string.Empty;
public Guid AccountId { get; set; }
[JsonIgnore] public Account.Account Account { get; set; } = null!;
}
public class AuthClientWithChallenge : AuthClient
{
public List<AuthChallenge> Challenges { get; set; } = [];
public static AuthClientWithChallenge FromClient(AuthClient client)
{
return new AuthClientWithChallenge
{
Id = client.Id,
Platform = client.Platform,
DeviceName = client.DeviceName,
DeviceLabel = client.DeviceLabel,
DeviceId = client.DeviceId,
AccountId = client.AccountId,
};
}
}

View File

@@ -1,4 +1,5 @@
using System.Security.Cryptography;
using DysonNetwork.Shared.Models;
namespace DysonNetwork.Pass.Auth;
@@ -7,7 +8,7 @@ public class CompactTokenService(IConfiguration config)
private readonly string _privateKeyPath = config["AuthToken:PrivateKeyPath"]
?? throw new InvalidOperationException("AuthToken:PrivateKeyPath configuration is missing");
public string CreateToken(AuthSession session)
public string CreateToken(SnAuthSession session)
{
// Load the private key for signing
var privateKeyPem = File.ReadAllText(_privateKeyPath);

View File

@@ -8,10 +8,10 @@ using System.Text.Json.Serialization;
using System.Web;
using DysonNetwork.Pass.Account;
using DysonNetwork.Pass.Auth.OidcProvider.Options;
using DysonNetwork.Shared.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;
using NodaTime;
using DysonNetwork.Shared.Models;
namespace DysonNetwork.Pass.Auth.OidcProvider.Controllers;
@@ -98,9 +98,9 @@ public class OidcProviderController(
var clientInfo = new ClientInfoResponse
{
ClientId = Guid.Parse(client.Id),
Picture = client.Picture is not null ? CloudFileReferenceObject.FromProtoValue(client.Picture) : null,
Picture = client.Picture is not null ? SnCloudFileReferenceObject.FromProtoValue(client.Picture) : null,
Background = client.Background is not null
? CloudFileReferenceObject.FromProtoValue(client.Background)
? SnCloudFileReferenceObject.FromProtoValue(client.Background)
: null,
ClientName = client.Name,
HomeUri = client.Links.HomePage,
@@ -304,7 +304,7 @@ public class OidcProviderController(
public async Task<IActionResult> GetUserInfo()
{
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser ||
HttpContext.Items["CurrentSession"] is not AuthSession currentSession) return Unauthorized();
HttpContext.Items["CurrentSession"] is not SnAuthSession currentSession) return Unauthorized();
// Get requested scopes from the token
var scopes = currentSession.Challenge?.Scopes ?? [];

View File

@@ -1,13 +1,13 @@
using System.Text.Json.Serialization;
using DysonNetwork.Shared.Data;
using DysonNetwork.Shared.Models;
namespace DysonNetwork.Pass.Auth.OidcProvider.Responses;
public class ClientInfoResponse
{
public Guid ClientId { get; set; }
public CloudFileReferenceObject? Picture { get; set; }
public CloudFileReferenceObject? Background { get; set; }
public SnCloudFileReferenceObject? Picture { get; set; }
public SnCloudFileReferenceObject? Background { get; set; }
public string? ClientName { get; set; }
public string? HomeUri { get; set; }
public string? PolicyUri { get; set; }

View File

@@ -6,6 +6,7 @@ using DysonNetwork.Pass.Auth.OidcProvider.Models;
using DysonNetwork.Pass.Auth.OidcProvider.Options;
using DysonNetwork.Pass.Auth.OidcProvider.Responses;
using DysonNetwork.Shared.Cache;
using DysonNetwork.Shared.Models;
using DysonNetwork.Shared.Proto;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;

View File

@@ -1,5 +1,6 @@
using DysonNetwork.Pass.Account;
using DysonNetwork.Shared.Cache;
using DysonNetwork.Shared.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;
@@ -68,7 +69,7 @@ public class OidcController(
/// Handles Apple authentication directly from mobile apps
/// </summary>
[HttpPost("apple/mobile")]
public async Task<ActionResult<AuthChallenge>> AppleMobileLogin(
public async Task<ActionResult<SnAuthChallenge>> AppleMobileLogin(
[FromBody] AppleMobileSignInRequest request
)
{

View File

@@ -2,6 +2,7 @@ using System.IdentityModel.Tokens.Jwt;
using System.Text.Json.Serialization;
using DysonNetwork.Pass.Account;
using DysonNetwork.Shared.Cache;
using DysonNetwork.Shared.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;
using NodaTime;
@@ -187,7 +188,7 @@ public abstract class OidcService(
/// Creates a challenge and session for an authenticated user
/// Also creates or updates the account connection
/// </summary>
public async Task<AuthChallenge> CreateChallengeForUserAsync(
public async Task<SnAuthChallenge> CreateChallengeForUserAsync(
OidcUserInfo userInfo,
Account.Account account,
HttpContext request,
@@ -219,7 +220,7 @@ public abstract class OidcService(
// Create a challenge that's already completed
var now = SystemClock.Instance.GetCurrentInstant();
var device = await auth.GetOrCreateDeviceAsync(account.Id, deviceId, deviceName, ClientPlatform.Ios);
var challenge = new AuthChallenge
var challenge = new SnAuthChallenge
{
ExpiredAt = now.Plus(Duration.FromHours(1)),
StepTotal = await auth.DetectChallengeRisk(request.Request, account),

View File

@@ -3,6 +3,7 @@ using System.Security.Cryptography;
using System.Text;
using DysonNetwork.Pass.Wallet;
using DysonNetwork.Shared.Cache;
using DysonNetwork.Shared.Models;
using Microsoft.EntityFrameworkCore;
using NodaTime;
@@ -25,7 +26,7 @@ public class TokenAuthService(
/// <param name="token">Incoming token string</param>
/// <param name="ipAddress">Client IP address, for logging purposes</param>
/// <returns>(Valid, Session, Message)</returns>
public async Task<(bool Valid, AuthSession? Session, string? Message)> AuthenticateTokenAsync(string token, string? ipAddress = null)
public async Task<(bool Valid, SnAuthSession? Session, string? Message)> AuthenticateTokenAsync(string token, string? ipAddress = null)
{
try
{
@@ -63,7 +64,7 @@ public class TokenAuthService(
// Try cache first
var cacheKey = $"{AuthCacheConstants.Prefix}{sessionId}";
var session = await cache.GetAsync<AuthSession>(cacheKey);
var session = await cache.GetAsync<SnAuthSession>(cacheKey);
if (session is not null)
{
logger.LogDebug("AuthenticateTokenAsync: cache hit for {CacheKey}", cacheKey);

View File

@@ -1,34 +0,0 @@
using System.ComponentModel.DataAnnotations;
using DysonNetwork.Shared.Data;
using NodaTime;
using NodaTime.Serialization.Protobuf;
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!;
public Shared.Proto.SocialCreditRecord ToProto()
{
var proto = new Shared.Proto.SocialCreditRecord
{
Id = Id.ToString(),
ReasonType = ReasonType,
Reason = Reason,
Delta = Delta,
AccountId = AccountId.ToString(),
CreatedAt = CreatedAt.ToTimestamp(),
UpdatedAt = UpdatedAt.ToTimestamp()
};
return proto;
}
}

View File

@@ -1,4 +1,5 @@
using DysonNetwork.Shared.Cache;
using DysonNetwork.Shared.Models;
using Microsoft.EntityFrameworkCore;
namespace DysonNetwork.Pass.Credit;

View File

@@ -1,4 +1,5 @@
using DysonNetwork.Shared.Cache;
using DysonNetwork.Shared.Models;
using Microsoft.EntityFrameworkCore;
using NodaTime;
using Quartz;
@@ -7,7 +8,7 @@ namespace DysonNetwork.Pass.Handlers;
public class LastActiveInfo
{
public Auth.AuthSession Session { get; set; } = null!;
public SnAuthSession Session { get; set; } = null!;
public Account.Account Account { get; set; } = null!;
public Instant SeenAt { get; set; }
}

View File

@@ -1,34 +0,0 @@
using System.ComponentModel.DataAnnotations;
using DysonNetwork.Shared.Data;
using NodaTime.Serialization.Protobuf;
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!;
public Shared.Proto.ExperienceRecord ToProto()
{
var proto = new Shared.Proto.ExperienceRecord
{
Id = Id.ToString(),
ReasonType = ReasonType,
Reason = Reason,
Delta = Delta,
BonusMultiplier = BonusMultiplier,
AccountId = AccountId.ToString(),
CreatedAt = CreatedAt.ToTimestamp(),
UpdatedAt = UpdatedAt.ToTimestamp()
};
return proto;
}
}

View File

@@ -1,5 +1,6 @@
using DysonNetwork.Pass.Wallet;
using DysonNetwork.Shared.Cache;
using DysonNetwork.Shared.Models;
using Microsoft.EntityFrameworkCore;
namespace DysonNetwork.Pass.Leveling;

View File

@@ -4,8 +4,7 @@ using System.Collections.Generic;
using System.Text.Json;
using DysonNetwork.Pass;
using DysonNetwork.Pass.Account;
using DysonNetwork.Pass.Wallet;
using DysonNetwork.Shared.Data;
using DysonNetwork.Shared.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
@@ -390,7 +389,7 @@ namespace DysonNetwork.Pass.Migrations
.HasColumnType("jsonb")
.HasColumnName("active_badge");
b.Property<CloudFileReferenceObject>("Background")
b.Property<SnCloudFileReferenceObject>("Background")
.HasColumnType("jsonb")
.HasColumnName("background");
@@ -449,7 +448,7 @@ namespace DysonNetwork.Pass.Migrations
.HasColumnType("character varying(256)")
.HasColumnName("middle_name");
b.Property<CloudFileReferenceObject>("Picture")
b.Property<SnCloudFileReferenceObject>("Picture")
.HasColumnType("jsonb")
.HasColumnName("picture");
@@ -472,7 +471,7 @@ namespace DysonNetwork.Pass.Migrations
.HasColumnType("timestamp with time zone")
.HasColumnName("updated_at");
b.Property<VerificationMark>("Verification")
b.Property<SnVerificationMark>("Verification")
.HasColumnType("jsonb")
.HasColumnName("verification");
@@ -1029,7 +1028,7 @@ namespace DysonNetwork.Pass.Migrations
.HasColumnType("uuid")
.HasColumnName("id");
b.Property<CloudFileReferenceObject>("Background")
b.Property<SnCloudFileReferenceObject>("Background")
.HasColumnType("jsonb")
.HasColumnName("background");
@@ -1052,7 +1051,7 @@ namespace DysonNetwork.Pass.Migrations
.HasColumnType("character varying(1024)")
.HasColumnName("name");
b.Property<CloudFileReferenceObject>("Picture")
b.Property<SnCloudFileReferenceObject>("Picture")
.HasColumnType("jsonb")
.HasColumnName("picture");
@@ -1070,7 +1069,7 @@ namespace DysonNetwork.Pass.Migrations
.HasColumnType("timestamp with time zone")
.HasColumnName("updated_at");
b.Property<VerificationMark>("Verification")
b.Property<SnVerificationMark>("Verification")
.HasColumnType("jsonb")
.HasColumnName("verification");

View File

@@ -2,8 +2,7 @@
using System.Collections.Generic;
using System.Text.Json;
using DysonNetwork.Pass.Account;
using DysonNetwork.Pass.Wallet;
using DysonNetwork.Shared.Data;
using DysonNetwork.Shared.Models;
using Microsoft.EntityFrameworkCore.Migrations;
using NetTopologySuite.Geometries;
using NodaTime;
@@ -49,9 +48,9 @@ namespace DysonNetwork.Pass.Migrations
name = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false),
description = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: true),
status = table.Column<int>(type: "integer", nullable: false),
picture = table.Column<CloudFileReferenceObject>(type: "jsonb", nullable: true),
background = table.Column<CloudFileReferenceObject>(type: "jsonb", nullable: true),
verification = table.Column<VerificationMark>(type: "jsonb", nullable: true),
picture = table.Column<SnCloudFileReferenceObject>(type: "jsonb", nullable: true),
background = table.Column<SnCloudFileReferenceObject>(type: "jsonb", nullable: true),
verification = table.Column<SnVerificationMark>(type: "jsonb", nullable: true),
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)
@@ -242,13 +241,13 @@ namespace DysonNetwork.Pass.Migrations
location = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: true),
birthday = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
last_seen_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
verification = table.Column<VerificationMark>(type: "jsonb", nullable: true),
verification = table.Column<SnVerificationMark>(type: "jsonb", nullable: true),
active_badge = table.Column<BadgeReferenceObject>(type: "jsonb", nullable: true),
experience = table.Column<int>(type: "integer", nullable: false),
picture_id = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: true),
background_id = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: true),
picture = table.Column<CloudFileReferenceObject>(type: "jsonb", nullable: true),
background = table.Column<CloudFileReferenceObject>(type: "jsonb", nullable: true),
picture = table.Column<SnCloudFileReferenceObject>(type: "jsonb", nullable: true),
background = table.Column<SnCloudFileReferenceObject>(type: "jsonb", 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),

View File

@@ -4,8 +4,7 @@ using System.Collections.Generic;
using System.Text.Json;
using DysonNetwork.Pass;
using DysonNetwork.Pass.Account;
using DysonNetwork.Pass.Wallet;
using DysonNetwork.Shared.Data;
using DysonNetwork.Shared.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
@@ -390,7 +389,7 @@ namespace DysonNetwork.Pass.Migrations
.HasColumnType("jsonb")
.HasColumnName("active_badge");
b.Property<CloudFileReferenceObject>("Background")
b.Property<SnCloudFileReferenceObject>("Background")
.HasColumnType("jsonb")
.HasColumnName("background");
@@ -444,7 +443,7 @@ namespace DysonNetwork.Pass.Migrations
.HasColumnType("character varying(256)")
.HasColumnName("middle_name");
b.Property<CloudFileReferenceObject>("Picture")
b.Property<SnCloudFileReferenceObject>("Picture")
.HasColumnType("jsonb")
.HasColumnName("picture");
@@ -462,7 +461,7 @@ namespace DysonNetwork.Pass.Migrations
.HasColumnType("timestamp with time zone")
.HasColumnName("updated_at");
b.Property<VerificationMark>("Verification")
b.Property<SnVerificationMark>("Verification")
.HasColumnType("jsonb")
.HasColumnName("verification");
@@ -1019,7 +1018,7 @@ namespace DysonNetwork.Pass.Migrations
.HasColumnType("uuid")
.HasColumnName("id");
b.Property<CloudFileReferenceObject>("Background")
b.Property<SnCloudFileReferenceObject>("Background")
.HasColumnType("jsonb")
.HasColumnName("background");
@@ -1042,7 +1041,7 @@ namespace DysonNetwork.Pass.Migrations
.HasColumnType("character varying(1024)")
.HasColumnName("name");
b.Property<CloudFileReferenceObject>("Picture")
b.Property<SnCloudFileReferenceObject>("Picture")
.HasColumnType("jsonb")
.HasColumnName("picture");
@@ -1060,7 +1059,7 @@ namespace DysonNetwork.Pass.Migrations
.HasColumnType("timestamp with time zone")
.HasColumnName("updated_at");
b.Property<VerificationMark>("Verification")
b.Property<SnVerificationMark>("Verification")
.HasColumnType("jsonb")
.HasColumnName("verification");

View File

@@ -4,8 +4,7 @@ using System.Collections.Generic;
using System.Text.Json;
using DysonNetwork.Pass;
using DysonNetwork.Pass.Account;
using DysonNetwork.Pass.Wallet;
using DysonNetwork.Shared.Data;
using DysonNetwork.Shared.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
@@ -390,7 +389,7 @@ namespace DysonNetwork.Pass.Migrations
.HasColumnType("jsonb")
.HasColumnName("active_badge");
b.Property<CloudFileReferenceObject>("Background")
b.Property<SnCloudFileReferenceObject>("Background")
.HasColumnType("jsonb")
.HasColumnName("background");
@@ -444,7 +443,7 @@ namespace DysonNetwork.Pass.Migrations
.HasColumnType("character varying(256)")
.HasColumnName("middle_name");
b.Property<CloudFileReferenceObject>("Picture")
b.Property<SnCloudFileReferenceObject>("Picture")
.HasColumnType("jsonb")
.HasColumnName("picture");
@@ -462,7 +461,7 @@ namespace DysonNetwork.Pass.Migrations
.HasColumnType("timestamp with time zone")
.HasColumnName("updated_at");
b.Property<VerificationMark>("Verification")
b.Property<SnVerificationMark>("Verification")
.HasColumnType("jsonb")
.HasColumnName("verification");
@@ -898,7 +897,7 @@ namespace DysonNetwork.Pass.Migrations
.HasColumnType("uuid")
.HasColumnName("id");
b.Property<CloudFileReferenceObject>("Background")
b.Property<SnCloudFileReferenceObject>("Background")
.HasColumnType("jsonb")
.HasColumnName("background");
@@ -921,7 +920,7 @@ namespace DysonNetwork.Pass.Migrations
.HasColumnType("character varying(1024)")
.HasColumnName("name");
b.Property<CloudFileReferenceObject>("Picture")
b.Property<SnCloudFileReferenceObject>("Picture")
.HasColumnType("jsonb")
.HasColumnName("picture");
@@ -939,7 +938,7 @@ namespace DysonNetwork.Pass.Migrations
.HasColumnType("timestamp with time zone")
.HasColumnName("updated_at");
b.Property<VerificationMark>("Verification")
b.Property<SnVerificationMark>("Verification")
.HasColumnType("jsonb")
.HasColumnName("verification");

View File

@@ -4,8 +4,7 @@ using System.Collections.Generic;
using System.Text.Json;
using DysonNetwork.Pass;
using DysonNetwork.Pass.Account;
using DysonNetwork.Pass.Wallet;
using DysonNetwork.Shared.Data;
using DysonNetwork.Shared.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
@@ -390,7 +389,7 @@ namespace DysonNetwork.Pass.Migrations
.HasColumnType("jsonb")
.HasColumnName("active_badge");
b.Property<CloudFileReferenceObject>("Background")
b.Property<SnCloudFileReferenceObject>("Background")
.HasColumnType("jsonb")
.HasColumnName("background");
@@ -444,7 +443,7 @@ namespace DysonNetwork.Pass.Migrations
.HasColumnType("character varying(256)")
.HasColumnName("middle_name");
b.Property<CloudFileReferenceObject>("Picture")
b.Property<SnCloudFileReferenceObject>("Picture")
.HasColumnType("jsonb")
.HasColumnName("picture");
@@ -462,7 +461,7 @@ namespace DysonNetwork.Pass.Migrations
.HasColumnType("timestamp with time zone")
.HasColumnName("updated_at");
b.Property<VerificationMark>("Verification")
b.Property<SnVerificationMark>("Verification")
.HasColumnType("jsonb")
.HasColumnName("verification");
@@ -902,7 +901,7 @@ namespace DysonNetwork.Pass.Migrations
.HasColumnType("uuid")
.HasColumnName("id");
b.Property<CloudFileReferenceObject>("Background")
b.Property<SnCloudFileReferenceObject>("Background")
.HasColumnType("jsonb")
.HasColumnName("background");
@@ -925,7 +924,7 @@ namespace DysonNetwork.Pass.Migrations
.HasColumnType("character varying(1024)")
.HasColumnName("name");
b.Property<CloudFileReferenceObject>("Picture")
b.Property<SnCloudFileReferenceObject>("Picture")
.HasColumnType("jsonb")
.HasColumnName("picture");
@@ -943,7 +942,7 @@ namespace DysonNetwork.Pass.Migrations
.HasColumnType("timestamp with time zone")
.HasColumnName("updated_at");
b.Property<VerificationMark>("Verification")
b.Property<SnVerificationMark>("Verification")
.HasColumnType("jsonb")
.HasColumnName("verification");

View File

@@ -4,8 +4,7 @@ using System.Collections.Generic;
using System.Text.Json;
using DysonNetwork.Pass;
using DysonNetwork.Pass.Account;
using DysonNetwork.Pass.Wallet;
using DysonNetwork.Shared.Data;
using DysonNetwork.Shared.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
@@ -390,7 +389,7 @@ namespace DysonNetwork.Pass.Migrations
.HasColumnType("jsonb")
.HasColumnName("active_badge");
b.Property<CloudFileReferenceObject>("Background")
b.Property<SnCloudFileReferenceObject>("Background")
.HasColumnType("jsonb")
.HasColumnName("background");
@@ -444,7 +443,7 @@ namespace DysonNetwork.Pass.Migrations
.HasColumnType("character varying(256)")
.HasColumnName("middle_name");
b.Property<CloudFileReferenceObject>("Picture")
b.Property<SnCloudFileReferenceObject>("Picture")
.HasColumnType("jsonb")
.HasColumnName("picture");
@@ -462,7 +461,7 @@ namespace DysonNetwork.Pass.Migrations
.HasColumnType("timestamp with time zone")
.HasColumnName("updated_at");
b.Property<VerificationMark>("Verification")
b.Property<SnVerificationMark>("Verification")
.HasColumnType("jsonb")
.HasColumnName("verification");

View File

@@ -1,6 +1,6 @@
using System;
using System.Collections.Generic;
using DysonNetwork.Shared.Data;
using DysonNetwork.Shared.Models;
using Microsoft.EntityFrameworkCore.Migrations;
using NodaTime;
@@ -70,16 +70,16 @@ namespace DysonNetwork.Pass.Migrations
columns: table => new
{
id = table.Column<Guid>(type: "uuid", nullable: false),
background = table.Column<CloudFileReferenceObject>(type: "jsonb", nullable: true),
background = table.Column<SnCloudFileReferenceObject>(type: "jsonb", nullable: true),
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
description = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: true),
name = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false),
picture = table.Column<CloudFileReferenceObject>(type: "jsonb", nullable: true),
picture = table.Column<SnCloudFileReferenceObject>(type: "jsonb", nullable: true),
slug = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false),
status = table.Column<int>(type: "integer", nullable: false),
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
verification = table.Column<VerificationMark>(type: "jsonb", nullable: true)
verification = table.Column<SnVerificationMark>(type: "jsonb", nullable: true)
},
constraints: table =>
{

View File

@@ -4,8 +4,7 @@ using System.Collections.Generic;
using System.Text.Json;
using DysonNetwork.Pass;
using DysonNetwork.Pass.Account;
using DysonNetwork.Pass.Wallet;
using DysonNetwork.Shared.Data;
using DysonNetwork.Shared.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
@@ -390,7 +389,7 @@ namespace DysonNetwork.Pass.Migrations
.HasColumnType("jsonb")
.HasColumnName("active_badge");
b.Property<CloudFileReferenceObject>("Background")
b.Property<SnCloudFileReferenceObject>("Background")
.HasColumnType("jsonb")
.HasColumnName("background");
@@ -448,7 +447,7 @@ namespace DysonNetwork.Pass.Migrations
.HasColumnType("character varying(256)")
.HasColumnName("middle_name");
b.Property<CloudFileReferenceObject>("Picture")
b.Property<SnCloudFileReferenceObject>("Picture")
.HasColumnType("jsonb")
.HasColumnName("picture");
@@ -466,7 +465,7 @@ namespace DysonNetwork.Pass.Migrations
.HasColumnType("timestamp with time zone")
.HasColumnName("updated_at");
b.Property<VerificationMark>("Verification")
b.Property<SnVerificationMark>("Verification")
.HasColumnType("jsonb")
.HasColumnName("verification");

View File

@@ -4,8 +4,7 @@ using System.Collections.Generic;
using System.Text.Json;
using DysonNetwork.Pass;
using DysonNetwork.Pass.Account;
using DysonNetwork.Pass.Wallet;
using DysonNetwork.Shared.Data;
using DysonNetwork.Shared.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
@@ -394,7 +393,7 @@ namespace DysonNetwork.Pass.Migrations
.HasColumnType("jsonb")
.HasColumnName("active_badge");
b.Property<CloudFileReferenceObject>("Background")
b.Property<SnCloudFileReferenceObject>("Background")
.HasColumnType("jsonb")
.HasColumnName("background");
@@ -452,7 +451,7 @@ namespace DysonNetwork.Pass.Migrations
.HasColumnType("character varying(256)")
.HasColumnName("middle_name");
b.Property<CloudFileReferenceObject>("Picture")
b.Property<SnCloudFileReferenceObject>("Picture")
.HasColumnType("jsonb")
.HasColumnName("picture");
@@ -470,7 +469,7 @@ namespace DysonNetwork.Pass.Migrations
.HasColumnType("timestamp with time zone")
.HasColumnName("updated_at");
b.Property<VerificationMark>("Verification")
b.Property<SnVerificationMark>("Verification")
.HasColumnType("jsonb")
.HasColumnName("verification");

View File

@@ -4,8 +4,7 @@ using System.Collections.Generic;
using System.Text.Json;
using DysonNetwork.Pass;
using DysonNetwork.Pass.Account;
using DysonNetwork.Pass.Wallet;
using DysonNetwork.Shared.Data;
using DysonNetwork.Shared.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
@@ -394,7 +393,7 @@ namespace DysonNetwork.Pass.Migrations
.HasColumnType("jsonb")
.HasColumnName("active_badge");
b.Property<CloudFileReferenceObject>("Background")
b.Property<SnCloudFileReferenceObject>("Background")
.HasColumnType("jsonb")
.HasColumnName("background");
@@ -452,7 +451,7 @@ namespace DysonNetwork.Pass.Migrations
.HasColumnType("character varying(256)")
.HasColumnName("middle_name");
b.Property<CloudFileReferenceObject>("Picture")
b.Property<SnCloudFileReferenceObject>("Picture")
.HasColumnType("jsonb")
.HasColumnName("picture");
@@ -470,7 +469,7 @@ namespace DysonNetwork.Pass.Migrations
.HasColumnType("timestamp with time zone")
.HasColumnName("updated_at");
b.Property<VerificationMark>("Verification")
b.Property<SnVerificationMark>("Verification")
.HasColumnType("jsonb")
.HasColumnName("verification");

View File

@@ -4,8 +4,7 @@ using System.Collections.Generic;
using System.Text.Json;
using DysonNetwork.Pass;
using DysonNetwork.Pass.Account;
using DysonNetwork.Pass.Wallet;
using DysonNetwork.Shared.Data;
using DysonNetwork.Shared.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
@@ -394,7 +393,7 @@ namespace DysonNetwork.Pass.Migrations
.HasColumnType("jsonb")
.HasColumnName("active_badge");
b.Property<CloudFileReferenceObject>("Background")
b.Property<SnCloudFileReferenceObject>("Background")
.HasColumnType("jsonb")
.HasColumnName("background");
@@ -452,7 +451,7 @@ namespace DysonNetwork.Pass.Migrations
.HasColumnType("character varying(256)")
.HasColumnName("middle_name");
b.Property<CloudFileReferenceObject>("Picture")
b.Property<SnCloudFileReferenceObject>("Picture")
.HasColumnType("jsonb")
.HasColumnName("picture");
@@ -470,7 +469,7 @@ namespace DysonNetwork.Pass.Migrations
.HasColumnType("timestamp with time zone")
.HasColumnName("updated_at");
b.Property<VerificationMark>("Verification")
b.Property<SnVerificationMark>("Verification")
.HasColumnType("jsonb")
.HasColumnName("verification");

View File

@@ -4,8 +4,7 @@ using System.Collections.Generic;
using System.Text.Json;
using DysonNetwork.Pass;
using DysonNetwork.Pass.Account;
using DysonNetwork.Pass.Wallet;
using DysonNetwork.Shared.Data;
using DysonNetwork.Shared.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
@@ -394,7 +393,7 @@ namespace DysonNetwork.Pass.Migrations
.HasColumnType("jsonb")
.HasColumnName("active_badge");
b.Property<CloudFileReferenceObject>("Background")
b.Property<SnCloudFileReferenceObject>("Background")
.HasColumnType("jsonb")
.HasColumnName("background");
@@ -452,7 +451,7 @@ namespace DysonNetwork.Pass.Migrations
.HasColumnType("character varying(256)")
.HasColumnName("middle_name");
b.Property<CloudFileReferenceObject>("Picture")
b.Property<SnCloudFileReferenceObject>("Picture")
.HasColumnType("jsonb")
.HasColumnName("picture");
@@ -470,7 +469,7 @@ namespace DysonNetwork.Pass.Migrations
.HasColumnType("timestamp with time zone")
.HasColumnName("updated_at");
b.Property<VerificationMark>("Verification")
b.Property<SnVerificationMark>("Verification")
.HasColumnType("jsonb")
.HasColumnName("verification");

View File

@@ -4,8 +4,7 @@ using System.Collections.Generic;
using System.Text.Json;
using DysonNetwork.Pass;
using DysonNetwork.Pass.Account;
using DysonNetwork.Pass.Wallet;
using DysonNetwork.Shared.Data;
using DysonNetwork.Shared.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
@@ -394,7 +393,7 @@ namespace DysonNetwork.Pass.Migrations
.HasColumnType("jsonb")
.HasColumnName("active_badge");
b.Property<CloudFileReferenceObject>("Background")
b.Property<SnCloudFileReferenceObject>("Background")
.HasColumnType("jsonb")
.HasColumnName("background");
@@ -452,7 +451,7 @@ namespace DysonNetwork.Pass.Migrations
.HasColumnType("character varying(256)")
.HasColumnName("middle_name");
b.Property<CloudFileReferenceObject>("Picture")
b.Property<SnCloudFileReferenceObject>("Picture")
.HasColumnType("jsonb")
.HasColumnName("picture");
@@ -470,7 +469,7 @@ namespace DysonNetwork.Pass.Migrations
.HasColumnType("timestamp with time zone")
.HasColumnName("updated_at");
b.Property<VerificationMark>("Verification")
b.Property<SnVerificationMark>("Verification")
.HasColumnType("jsonb")
.HasColumnName("verification");

View File

@@ -4,8 +4,7 @@ using System.Collections.Generic;
using System.Text.Json;
using DysonNetwork.Pass;
using DysonNetwork.Pass.Account;
using DysonNetwork.Pass.Wallet;
using DysonNetwork.Shared.Data;
using DysonNetwork.Shared.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
@@ -398,7 +397,7 @@ namespace DysonNetwork.Pass.Migrations
.HasColumnType("jsonb")
.HasColumnName("active_badge");
b.Property<CloudFileReferenceObject>("Background")
b.Property<SnCloudFileReferenceObject>("Background")
.HasColumnType("jsonb")
.HasColumnName("background");
@@ -456,7 +455,7 @@ namespace DysonNetwork.Pass.Migrations
.HasColumnType("character varying(256)")
.HasColumnName("middle_name");
b.Property<CloudFileReferenceObject>("Picture")
b.Property<SnCloudFileReferenceObject>("Picture")
.HasColumnType("jsonb")
.HasColumnName("picture");
@@ -474,7 +473,7 @@ namespace DysonNetwork.Pass.Migrations
.HasColumnType("timestamp with time zone")
.HasColumnName("updated_at");
b.Property<VerificationMark>("Verification")
b.Property<SnVerificationMark>("Verification")
.HasColumnType("jsonb")
.HasColumnName("verification");

View File

@@ -4,8 +4,7 @@ using System.Collections.Generic;
using System.Text.Json;
using DysonNetwork.Pass;
using DysonNetwork.Pass.Account;
using DysonNetwork.Pass.Wallet;
using DysonNetwork.Shared.Data;
using DysonNetwork.Shared.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
@@ -398,7 +397,7 @@ namespace DysonNetwork.Pass.Migrations
.HasColumnType("jsonb")
.HasColumnName("active_badge");
b.Property<CloudFileReferenceObject>("Background")
b.Property<SnCloudFileReferenceObject>("Background")
.HasColumnType("jsonb")
.HasColumnName("background");
@@ -456,7 +455,7 @@ namespace DysonNetwork.Pass.Migrations
.HasColumnType("character varying(256)")
.HasColumnName("middle_name");
b.Property<CloudFileReferenceObject>("Picture")
b.Property<SnCloudFileReferenceObject>("Picture")
.HasColumnType("jsonb")
.HasColumnName("picture");
@@ -474,7 +473,7 @@ namespace DysonNetwork.Pass.Migrations
.HasColumnType("timestamp with time zone")
.HasColumnName("updated_at");
b.Property<VerificationMark>("Verification")
b.Property<SnVerificationMark>("Verification")
.HasColumnType("jsonb")
.HasColumnName("verification");

View File

@@ -4,8 +4,7 @@ using System.Collections.Generic;
using System.Text.Json;
using DysonNetwork.Pass;
using DysonNetwork.Pass.Account;
using DysonNetwork.Pass.Wallet;
using DysonNetwork.Shared.Data;
using DysonNetwork.Shared.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
@@ -398,7 +397,7 @@ namespace DysonNetwork.Pass.Migrations
.HasColumnType("jsonb")
.HasColumnName("active_badge");
b.Property<CloudFileReferenceObject>("Background")
b.Property<SnCloudFileReferenceObject>("Background")
.HasColumnType("jsonb")
.HasColumnName("background");
@@ -456,7 +455,7 @@ namespace DysonNetwork.Pass.Migrations
.HasColumnType("character varying(256)")
.HasColumnName("middle_name");
b.Property<CloudFileReferenceObject>("Picture")
b.Property<SnCloudFileReferenceObject>("Picture")
.HasColumnType("jsonb")
.HasColumnName("picture");
@@ -474,7 +473,7 @@ namespace DysonNetwork.Pass.Migrations
.HasColumnType("timestamp with time zone")
.HasColumnName("updated_at");
b.Property<VerificationMark>("Verification")
b.Property<SnVerificationMark>("Verification")
.HasColumnType("jsonb")
.HasColumnName("verification");

View File

@@ -4,8 +4,7 @@ using System.Collections.Generic;
using System.Text.Json;
using DysonNetwork.Pass;
using DysonNetwork.Pass.Account;
using DysonNetwork.Pass.Wallet;
using DysonNetwork.Shared.Data;
using DysonNetwork.Shared.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
@@ -398,7 +397,7 @@ namespace DysonNetwork.Pass.Migrations
.HasColumnType("jsonb")
.HasColumnName("active_badge");
b.Property<CloudFileReferenceObject>("Background")
b.Property<SnCloudFileReferenceObject>("Background")
.HasColumnType("jsonb")
.HasColumnName("background");
@@ -456,7 +455,7 @@ namespace DysonNetwork.Pass.Migrations
.HasColumnType("character varying(256)")
.HasColumnName("middle_name");
b.Property<CloudFileReferenceObject>("Picture")
b.Property<SnCloudFileReferenceObject>("Picture")
.HasColumnType("jsonb")
.HasColumnName("picture");
@@ -474,7 +473,7 @@ namespace DysonNetwork.Pass.Migrations
.HasColumnType("timestamp with time zone")
.HasColumnName("updated_at");
b.Property<VerificationMark>("Verification")
b.Property<SnVerificationMark>("Verification")
.HasColumnType("jsonb")
.HasColumnName("verification");

View File

@@ -4,8 +4,7 @@ using System.Collections.Generic;
using System.Text.Json;
using DysonNetwork.Pass;
using DysonNetwork.Pass.Account;
using DysonNetwork.Pass.Wallet;
using DysonNetwork.Shared.Data;
using DysonNetwork.Shared.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
@@ -398,7 +397,7 @@ namespace DysonNetwork.Pass.Migrations
.HasColumnType("jsonb")
.HasColumnName("active_badge");
b.Property<CloudFileReferenceObject>("Background")
b.Property<SnCloudFileReferenceObject>("Background")
.HasColumnType("jsonb")
.HasColumnName("background");
@@ -456,7 +455,7 @@ namespace DysonNetwork.Pass.Migrations
.HasColumnType("character varying(256)")
.HasColumnName("middle_name");
b.Property<CloudFileReferenceObject>("Picture")
b.Property<SnCloudFileReferenceObject>("Picture")
.HasColumnType("jsonb")
.HasColumnName("picture");
@@ -478,7 +477,7 @@ namespace DysonNetwork.Pass.Migrations
.HasColumnType("timestamp with time zone")
.HasColumnName("updated_at");
b.Property<VerificationMark>("Verification")
b.Property<SnVerificationMark>("Verification")
.HasColumnType("jsonb")
.HasColumnName("verification");

View File

@@ -4,8 +4,7 @@ using System.Collections.Generic;
using System.Text.Json;
using DysonNetwork.Pass;
using DysonNetwork.Pass.Account;
using DysonNetwork.Pass.Wallet;
using DysonNetwork.Shared.Data;
using DysonNetwork.Shared.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
@@ -398,7 +397,7 @@ namespace DysonNetwork.Pass.Migrations
.HasColumnType("jsonb")
.HasColumnName("active_badge");
b.Property<CloudFileReferenceObject>("Background")
b.Property<SnCloudFileReferenceObject>("Background")
.HasColumnType("jsonb")
.HasColumnName("background");
@@ -456,7 +455,7 @@ namespace DysonNetwork.Pass.Migrations
.HasColumnType("character varying(256)")
.HasColumnName("middle_name");
b.Property<CloudFileReferenceObject>("Picture")
b.Property<SnCloudFileReferenceObject>("Picture")
.HasColumnType("jsonb")
.HasColumnName("picture");
@@ -478,7 +477,7 @@ namespace DysonNetwork.Pass.Migrations
.HasColumnType("timestamp with time zone")
.HasColumnName("updated_at");
b.Property<VerificationMark>("Verification")
b.Property<SnVerificationMark>("Verification")
.HasColumnType("jsonb")
.HasColumnName("verification");

View File

@@ -4,8 +4,7 @@ using System.Collections.Generic;
using System.Text.Json;
using DysonNetwork.Pass;
using DysonNetwork.Pass.Account;
using DysonNetwork.Pass.Wallet;
using DysonNetwork.Shared.Data;
using DysonNetwork.Shared.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
@@ -404,7 +403,7 @@ namespace DysonNetwork.Pass.Migrations
.HasColumnType("jsonb")
.HasColumnName("active_badge");
b.Property<CloudFileReferenceObject>("Background")
b.Property<SnCloudFileReferenceObject>("Background")
.HasColumnType("jsonb")
.HasColumnName("background");
@@ -462,7 +461,7 @@ namespace DysonNetwork.Pass.Migrations
.HasColumnType("character varying(256)")
.HasColumnName("middle_name");
b.Property<CloudFileReferenceObject>("Picture")
b.Property<SnCloudFileReferenceObject>("Picture")
.HasColumnType("jsonb")
.HasColumnName("picture");
@@ -484,7 +483,7 @@ namespace DysonNetwork.Pass.Migrations
.HasColumnType("timestamp with time zone")
.HasColumnName("updated_at");
b.Property<VerificationMark>("Verification")
b.Property<SnVerificationMark>("Verification")
.HasColumnType("jsonb")
.HasColumnName("verification");

View File

@@ -4,9 +4,8 @@ using System.Collections.Generic;
using System.Text.Json;
using DysonNetwork.Pass;
using DysonNetwork.Pass.Account;
using DysonNetwork.Pass.Wallet;
using DysonNetwork.Shared.Data;
using DysonNetwork.Shared.GeoIp;
using DysonNetwork.Shared.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
@@ -404,7 +403,7 @@ namespace DysonNetwork.Pass.Migrations
.HasColumnType("jsonb")
.HasColumnName("active_badge");
b.Property<CloudFileReferenceObject>("Background")
b.Property<SnCloudFileReferenceObject>("Background")
.HasColumnType("jsonb")
.HasColumnName("background");
@@ -462,7 +461,7 @@ namespace DysonNetwork.Pass.Migrations
.HasColumnType("character varying(256)")
.HasColumnName("middle_name");
b.Property<CloudFileReferenceObject>("Picture")
b.Property<SnCloudFileReferenceObject>("Picture")
.HasColumnType("jsonb")
.HasColumnName("picture");
@@ -484,7 +483,7 @@ namespace DysonNetwork.Pass.Migrations
.HasColumnType("timestamp with time zone")
.HasColumnName("updated_at");
b.Property<VerificationMark>("Verification")
b.Property<SnVerificationMark>("Verification")
.HasColumnType("jsonb")
.HasColumnName("verification");

View File

@@ -4,9 +4,8 @@ using System.Collections.Generic;
using System.Text.Json;
using DysonNetwork.Pass;
using DysonNetwork.Pass.Account;
using DysonNetwork.Pass.Wallet;
using DysonNetwork.Shared.Data;
using DysonNetwork.Shared.GeoIp;
using DysonNetwork.Shared.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
@@ -403,7 +402,7 @@ namespace DysonNetwork.Pass.Migrations
.HasColumnType("jsonb")
.HasColumnName("active_badge");
b.Property<CloudFileReferenceObject>("Background")
b.Property<SnCloudFileReferenceObject>("Background")
.HasColumnType("jsonb")
.HasColumnName("background");
@@ -461,7 +460,7 @@ namespace DysonNetwork.Pass.Migrations
.HasColumnType("character varying(256)")
.HasColumnName("middle_name");
b.Property<CloudFileReferenceObject>("Picture")
b.Property<SnCloudFileReferenceObject>("Picture")
.HasColumnType("jsonb")
.HasColumnName("picture");
@@ -483,7 +482,7 @@ namespace DysonNetwork.Pass.Migrations
.HasColumnType("timestamp with time zone")
.HasColumnName("updated_at");
b.Property<VerificationMark>("Verification")
b.Property<SnVerificationMark>("Verification")
.HasColumnType("jsonb")
.HasColumnName("verification");

View File

@@ -4,9 +4,8 @@ using System.Collections.Generic;
using System.Text.Json;
using DysonNetwork.Pass;
using DysonNetwork.Pass.Account;
using DysonNetwork.Pass.Wallet;
using DysonNetwork.Shared.Data;
using DysonNetwork.Shared.GeoIp;
using DysonNetwork.Shared.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
@@ -403,7 +402,7 @@ namespace DysonNetwork.Pass.Migrations
.HasColumnType("jsonb")
.HasColumnName("active_badge");
b.Property<CloudFileReferenceObject>("Background")
b.Property<SnCloudFileReferenceObject>("Background")
.HasColumnType("jsonb")
.HasColumnName("background");
@@ -461,7 +460,7 @@ namespace DysonNetwork.Pass.Migrations
.HasColumnType("character varying(256)")
.HasColumnName("middle_name");
b.Property<CloudFileReferenceObject>("Picture")
b.Property<SnCloudFileReferenceObject>("Picture")
.HasColumnType("jsonb")
.HasColumnName("picture");
@@ -483,7 +482,7 @@ namespace DysonNetwork.Pass.Migrations
.HasColumnType("timestamp with time zone")
.HasColumnName("updated_at");
b.Property<VerificationMark>("Verification")
b.Property<SnVerificationMark>("Verification")
.HasColumnType("jsonb")
.HasColumnName("verification");

View File

@@ -4,9 +4,8 @@ using System.Collections.Generic;
using System.Text.Json;
using DysonNetwork.Pass;
using DysonNetwork.Pass.Account;
using DysonNetwork.Pass.Wallet;
using DysonNetwork.Shared.Data;
using DysonNetwork.Shared.GeoIp;
using DysonNetwork.Shared.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
@@ -403,7 +402,7 @@ namespace DysonNetwork.Pass.Migrations
.HasColumnType("jsonb")
.HasColumnName("active_badge");
b.Property<CloudFileReferenceObject>("Background")
b.Property<SnCloudFileReferenceObject>("Background")
.HasColumnType("jsonb")
.HasColumnName("background");
@@ -461,7 +460,7 @@ namespace DysonNetwork.Pass.Migrations
.HasColumnType("character varying(256)")
.HasColumnName("middle_name");
b.Property<CloudFileReferenceObject>("Picture")
b.Property<SnCloudFileReferenceObject>("Picture")
.HasColumnType("jsonb")
.HasColumnName("picture");
@@ -483,7 +482,7 @@ namespace DysonNetwork.Pass.Migrations
.HasColumnType("timestamp with time zone")
.HasColumnName("updated_at");
b.Property<VerificationMark>("Verification")
b.Property<SnVerificationMark>("Verification")
.HasColumnType("jsonb")
.HasColumnName("verification");

View File

@@ -4,9 +4,8 @@ using System.Collections.Generic;
using System.Text.Json;
using DysonNetwork.Pass;
using DysonNetwork.Pass.Account;
using DysonNetwork.Pass.Wallet;
using DysonNetwork.Shared.Data;
using DysonNetwork.Shared.GeoIp;
using DysonNetwork.Shared.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
@@ -400,7 +399,7 @@ namespace DysonNetwork.Pass.Migrations
.HasColumnType("jsonb")
.HasColumnName("active_badge");
b.Property<CloudFileReferenceObject>("Background")
b.Property<SnCloudFileReferenceObject>("Background")
.HasColumnType("jsonb")
.HasColumnName("background");
@@ -458,7 +457,7 @@ namespace DysonNetwork.Pass.Migrations
.HasColumnType("character varying(256)")
.HasColumnName("middle_name");
b.Property<CloudFileReferenceObject>("Picture")
b.Property<SnCloudFileReferenceObject>("Picture")
.HasColumnType("jsonb")
.HasColumnName("picture");
@@ -480,7 +479,7 @@ namespace DysonNetwork.Pass.Migrations
.HasColumnType("timestamp with time zone")
.HasColumnName("updated_at");
b.Property<VerificationMark>("Verification")
b.Property<SnVerificationMark>("Verification")
.HasColumnType("jsonb")
.HasColumnName("verification");

View File

@@ -1,60 +0,0 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Text.Json;
using System.Text.Json.Serialization;
using DysonNetwork.Shared.Data;
using Microsoft.EntityFrameworkCore;
using NodaTime;
namespace DysonNetwork.Pass.Permission;
/// The permission node model provides the infrastructure of permission control in Dyson Network.
/// It based on the ABAC permission model.
///
/// The value can be any type, boolean and number for most cases and stored in jsonb.
///
/// The area represents the region this permission affects. For example, the pub:&lt;publisherId&gt;
/// indicates it's a permission node for the publishers managing.
///
/// And the actor shows who owns the permission, in most cases, the user:&lt;userId&gt;
/// and when the permission node has a GroupId, the actor will be set to the group, but it won't work on checking
/// expect the member of that permission group inherent the permission from the group.
[Index(nameof(Key), nameof(Area), nameof(Actor))]
public class PermissionNode : ModelBase, IDisposable
{
public Guid Id { get; set; } = Guid.NewGuid();
[MaxLength(1024)] public string Actor { get; set; } = null!;
[MaxLength(1024)] public string Area { get; set; } = null!;
[MaxLength(1024)] public string Key { get; set; } = null!;
[Column(TypeName = "jsonb")] public JsonDocument Value { get; set; } = null!;
public Instant? ExpiredAt { get; set; } = null;
public Instant? AffectedAt { get; set; } = null;
public Guid? GroupId { get; set; } = null;
[JsonIgnore] public PermissionGroup? Group { get; set; } = null;
public void Dispose()
{
Value.Dispose();
GC.SuppressFinalize(this);
}
}
public class PermissionGroup : ModelBase
{
public Guid Id { get; set; } = Guid.NewGuid();
[MaxLength(1024)] public string Key { get; set; } = null!;
public ICollection<PermissionNode> Nodes { get; set; } = new List<PermissionNode>();
[JsonIgnore] public ICollection<PermissionGroupMember> Members { get; set; } = new List<PermissionGroupMember>();
}
public class PermissionGroupMember : ModelBase
{
public Guid GroupId { get; set; }
public PermissionGroup Group { get; set; } = null!;
[MaxLength(1024)] public string Actor { get; set; } = null!;
public Instant? ExpiredAt { get; set; }
public Instant? AffectedAt { get; set; }
}

View File

@@ -3,6 +3,7 @@ using NodaTime;
using System.Text.Json;
using DysonNetwork.Pass;
using DysonNetwork.Shared.Cache;
using DysonNetwork.Shared.Models;
namespace DysonNetwork.Pass.Permission;
@@ -75,7 +76,7 @@ public class PermissionService(
return result;
}
public async Task<PermissionNode> AddPermissionNode<T>(
public async Task<SnPermissionNode> AddPermissionNode<T>(
string actor,
string area,
string key,
@@ -86,7 +87,7 @@ public class PermissionService(
{
if (value is null) throw new ArgumentNullException(nameof(value));
var node = new PermissionNode
var node = new SnPermissionNode
{
Actor = actor,
Key = key,
@@ -105,8 +106,8 @@ public class PermissionService(
return node;
}
public async Task<PermissionNode> AddPermissionNodeToGroup<T>(
PermissionGroup group,
public async Task<SnPermissionNode> AddPermissionNodeToGroup<T>(
SnPermissionGroup group,
string actor,
string area,
string key,
@@ -117,7 +118,7 @@ public class PermissionService(
{
if (value is null) throw new ArgumentNullException(nameof(value));
var node = new PermissionNode
var node = new SnPermissionNode
{
Actor = actor,
Key = key,
@@ -152,7 +153,7 @@ public class PermissionService(
await InvalidatePermissionCacheAsync(actor, area, key);
}
public async Task RemovePermissionNodeFromGroup<T>(PermissionGroup group, string actor, string area, string key)
public async Task RemovePermissionNodeFromGroup<T>(SnPermissionGroup group, string actor, string area, string key)
{
var node = await db.PermissionNodes
.Where(n => n.GroupId == group.Id)
@@ -185,9 +186,9 @@ public class PermissionService(
return JsonDocument.Parse(str);
}
public static PermissionNode NewPermissionNode<T>(string actor, string area, string key, T value)
public static SnPermissionNode NewPermissionNode<T>(string actor, string area, string key, T value)
{
return new PermissionNode
return new SnPermissionNode
{
Actor = actor,
Area = area,

View File

@@ -4,6 +4,7 @@ using DysonNetwork.Shared.Proto;
using Google.Protobuf.WellKnownTypes;
using System.Text.Json;
using NodaTime.Serialization.Protobuf;
using DysonNetwork.Shared.Models;
namespace DysonNetwork.Pass.Permission;

View File

@@ -1,5 +1,6 @@
using System.Text.Json;
using DysonNetwork.Pass.Wallet;
using DysonNetwork.Shared.Models;
using DysonNetwork.Shared.Proto;
using DysonNetwork.Shared.Stream;
using NATS.Client.Core;

View File

@@ -1,79 +0,0 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Text.Json.Serialization;
using DysonNetwork.Shared.Data;
using Microsoft.EntityFrameworkCore;
using NodaTime;
namespace DysonNetwork.Pass.Team;
public enum TeamType
{
Individual,
Organizational
}
[Index(nameof(Name), IsUnique = true)]
public class Team : ModelBase, IIdentifiedResource
{
public Guid Id { get; set; }
public TeamType Type { get; set; }
[MaxLength(256)] public string Name { get; set; } = string.Empty;
[MaxLength(256)] public string Nick { get; set; } = string.Empty;
[MaxLength(4096)] public string? Bio { get; set; }
[Column(TypeName = "jsonb")] public CloudFileReferenceObject? Picture { get; set; }
[Column(TypeName = "jsonb")] public CloudFileReferenceObject? Background { get; set; }
[Column(TypeName = "jsonb")] public VerificationMark? Verification { get; set; }
[JsonIgnore] public ICollection<TeamMember> Members { get; set; } = new List<TeamMember>();
[JsonIgnore] public ICollection<TeamFeature> Features { get; set; } = new List<TeamFeature>();
public Guid? AccountId { get; set; }
public Account.Account? Account { get; set; }
public string ResourceIdentifier => $"publisher/{Id}";
}
public enum TeamMemberRole
{
Owner = 100,
Manager = 75,
Editor = 50,
Viewer = 25
}
public class TeamMember : ModelBase
{
public Guid TeamId { get; set; }
[JsonIgnore] public Team Team { get; set; } = null!;
public Guid AccountId { get; set; }
public Account.Account Account { get; set; } = null!;
public TeamMemberRole Role { get; set; } = TeamMemberRole.Viewer;
public Instant? JoinedAt { get; set; }
}
public enum TeamSubscriptionStatus
{
Active,
Expired,
Cancelled
}
public class TeamFeature : ModelBase
{
public Guid Id { get; set; }
[MaxLength(1024)] public string Flag { get; set; } = null!;
public Instant? ExpiredAt { get; set; }
public Guid TeamId { get; set; }
public Team Team { get; set; } = null!;
}
public abstract class TeamFeatureFlag
{
public static List<string> AllFlags => [Develop];
public static string Develop = "develop";
}

View File

@@ -1,4 +1,5 @@
using DysonNetwork.Pass.Auth;
using DysonNetwork.Shared.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
@@ -10,7 +11,7 @@ namespace DysonNetwork.Pass.Wallet;
public class OrderController(PaymentService payment, AuthService auth, AppDatabase db) : ControllerBase
{
[HttpGet("{id:guid}")]
public async Task<ActionResult<Order>> GetOrderById(Guid id)
public async Task<ActionResult<SnWalletOrder>> GetOrderById(Guid id)
{
var order = await db.PaymentOrders.FindAsync(id);
@@ -22,7 +23,7 @@ public class OrderController(PaymentService payment, AuthService auth, AppDataba
[HttpPost("{id:guid}/pay")]
[Authorize]
public async Task<ActionResult<Order>> PayOrder(Guid id, [FromBody] PayOrderRequest request)
public async Task<ActionResult<SnWalletOrder>> PayOrder(Guid id, [FromBody] PayOrderRequest request)
{
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();

View File

@@ -1,126 +0,0 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Globalization;
using DysonNetwork.Shared.Data;
using NodaTime;
using NodaTime.Serialization.Protobuf;
namespace DysonNetwork.Pass.Wallet;
public class WalletCurrency
{
public const string SourcePoint = "points";
public const string GoldenPoint = "golds";
}
public enum OrderStatus
{
Unpaid,
Paid,
Cancelled,
Finished,
Expired
}
public class Order : ModelBase
{
public const string InternalAppIdentifier = "internal";
public Guid Id { get; set; } = Guid.NewGuid();
public OrderStatus Status { get; set; } = OrderStatus.Unpaid;
[MaxLength(128)] public string Currency { get; set; } = null!;
[MaxLength(4096)] public string? Remarks { get; set; }
[MaxLength(4096)] public string? AppIdentifier { get; set; }
[MaxLength(4096)] public string? ProductIdentifier { get; set; }
[Column(TypeName = "jsonb")] public Dictionary<string, object>? Meta { get; set; }
public decimal Amount { get; set; }
public Instant ExpiredAt { get; set; }
public Guid? PayeeWalletId { get; set; }
public Wallet? PayeeWallet { get; set; } = null!;
public Guid? TransactionId { get; set; }
public Transaction? Transaction { get; set; }
public Shared.Proto.Order ToProtoValue() => new()
{
Id = Id.ToString(),
Status = (Shared.Proto.OrderStatus)Status,
Currency = Currency,
Remarks = Remarks,
AppIdentifier = AppIdentifier,
ProductIdentifier = ProductIdentifier,
Meta = Meta == null
? null
: Google.Protobuf.ByteString.CopyFrom(System.Text.Json.JsonSerializer.SerializeToUtf8Bytes(Meta)),
Amount = Amount.ToString(CultureInfo.InvariantCulture),
ExpiredAt = ExpiredAt.ToTimestamp(),
PayeeWalletId = PayeeWalletId?.ToString(),
TransactionId = TransactionId?.ToString(),
Transaction = Transaction?.ToProtoValue(),
};
public static Order FromProtoValue(Shared.Proto.Order proto) => new()
{
Id = Guid.Parse(proto.Id),
Status = (OrderStatus)proto.Status,
Currency = proto.Currency,
Remarks = proto.Remarks,
AppIdentifier = proto.AppIdentifier,
ProductIdentifier = proto.ProductIdentifier,
Meta = proto.HasMeta
? System.Text.Json.JsonSerializer.Deserialize<Dictionary<string, object>>(proto.Meta.ToByteArray())
: null,
Amount = decimal.Parse(proto.Amount),
ExpiredAt = proto.ExpiredAt.ToInstant(),
PayeeWalletId = proto.PayeeWalletId is not null ? Guid.Parse(proto.PayeeWalletId) : null,
TransactionId = proto.TransactionId is not null ? Guid.Parse(proto.TransactionId) : null,
Transaction = proto.Transaction is not null ? Transaction.FromProtoValue(proto.Transaction) : null,
};
}
public enum TransactionType
{
System,
Transfer,
Order
}
public class Transaction : ModelBase
{
public Guid Id { get; set; } = Guid.NewGuid();
[MaxLength(128)] public string Currency { get; set; } = null!;
public decimal Amount { get; set; }
[MaxLength(4096)] public string? Remarks { get; set; }
public TransactionType Type { get; set; }
// When the payer is null, it's pay from the system
public Guid? PayerWalletId { get; set; }
public Wallet? PayerWallet { get; set; }
// When the payee is null, it's pay for the system
public Guid? PayeeWalletId { get; set; }
public Wallet? PayeeWallet { get; set; }
public Shared.Proto.Transaction ToProtoValue() => new()
{
Id = Id.ToString(),
Currency = Currency,
Amount = Amount.ToString(CultureInfo.InvariantCulture),
Remarks = Remarks,
Type = (Shared.Proto.TransactionType)Type,
PayerWalletId = PayerWalletId?.ToString(),
PayeeWalletId = PayeeWalletId?.ToString(),
};
public static Transaction FromProtoValue(Shared.Proto.Transaction proto) => new()
{
Id = Guid.Parse(proto.Id),
Currency = proto.Currency,
Amount = decimal.Parse(proto.Amount),
Remarks = proto.Remarks,
Type = (TransactionType)proto.Type,
PayerWalletId = proto.PayerWalletId is not null ? Guid.Parse(proto.PayerWalletId) : null,
PayeeWalletId = proto.PayeeWalletId is not null ? Guid.Parse(proto.PayeeWalletId) : null,
};
}

View File

@@ -1,6 +1,7 @@
using System.Globalization;
using System.Text.Json;
using DysonNetwork.Pass.Localization;
using DysonNetwork.Shared.Models;
using DysonNetwork.Shared.Proto;
using DysonNetwork.Shared.Stream;
using Microsoft.EntityFrameworkCore;

View File

@@ -1,3 +1,4 @@
using DysonNetwork.Shared.Models;
using DysonNetwork.Shared.Proto;
using Grpc.Core;
using NodaTime;

View File

@@ -1,405 +0,0 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Globalization;
using DysonNetwork.Shared.Data;
using Microsoft.EntityFrameworkCore;
using NodaTime;
using NodaTime.Serialization.Protobuf;
namespace DysonNetwork.Pass.Wallet;
public record class SubscriptionTypeData(
string Identifier,
string? GroupIdentifier,
string Currency,
decimal BasePrice,
int? RequiredLevel = null
)
{
public static readonly Dictionary<string, SubscriptionTypeData> SubscriptionDict =
new()
{
[SubscriptionType.Twinkle] = new SubscriptionTypeData(
SubscriptionType.Twinkle,
SubscriptionType.StellarProgram,
WalletCurrency.SourcePoint,
0,
1
),
[SubscriptionType.Stellar] = new SubscriptionTypeData(
SubscriptionType.Stellar,
SubscriptionType.StellarProgram,
WalletCurrency.SourcePoint,
1200,
3
),
[SubscriptionType.Nova] = new SubscriptionTypeData(
SubscriptionType.Nova,
SubscriptionType.StellarProgram,
WalletCurrency.SourcePoint,
2400,
6
),
[SubscriptionType.Supernova] = new SubscriptionTypeData(
SubscriptionType.Supernova,
SubscriptionType.StellarProgram,
WalletCurrency.SourcePoint,
3600,
9
)
};
public static readonly Dictionary<string, string> SubscriptionHumanReadable =
new()
{
[SubscriptionType.Twinkle] = "Stellar Program Twinkle",
[SubscriptionType.Stellar] = "Stellar Program",
[SubscriptionType.Nova] = "Stellar Program Nova",
[SubscriptionType.Supernova] = "Stellar Program Supernova"
};
}
public abstract class SubscriptionType
{
/// <summary>
/// DO NOT USE THIS TYPE DIRECTLY,
/// this is the prefix of all the stellar program subscriptions.
/// </summary>
public const string StellarProgram = "solian.stellar";
/// <summary>
/// No actual usage, just tells there is a free level named twinkle.
/// Applies to every registered user by default, so there is no need to create a record in db for that.
/// </summary>
public const string Twinkle = "solian.stellar.twinkle";
public const string Stellar = "solian.stellar.primary";
public const string Nova = "solian.stellar.nova";
public const string Supernova = "solian.stellar.supernova";
}
public abstract class SubscriptionPaymentMethod
{
/// <summary>
/// The solar points / solar dollars.
/// </summary>
public const string InAppWallet = "solian.wallet";
/// <summary>
/// afdian.com
/// aka. China patreon
/// </summary>
public const string Afdian = "afdian";
}
public enum SubscriptionStatus
{
Unpaid,
Active,
Expired,
Cancelled
}
/// <summary>
/// The subscription is for the Stellar Program in most cases.
/// The paid subscription in another word.
/// </summary>
[Index(nameof(Identifier))]
public class Subscription : ModelBase
{
public Guid Id { get; set; } = Guid.NewGuid();
public Instant BegunAt { get; set; }
public Instant? EndedAt { get; set; }
/// <summary>
/// The type of the subscriptions
/// </summary>
[MaxLength(4096)]
public string Identifier { get; set; } = null!;
/// <summary>
/// The field is used to override the activation status of the membership.
/// Might be used for refund handling and other special cases.
///
/// Go see the IsAvailable field if you want to get real the status of the membership.
/// </summary>
public bool IsActive { get; set; } = true;
/// <summary>
/// Indicates is the current user got the membership for free,
/// to prevent giving the same discount for the same user again.
/// </summary>
public bool IsFreeTrial { get; set; }
public SubscriptionStatus Status { get; set; } = SubscriptionStatus.Unpaid;
[MaxLength(4096)] public string PaymentMethod { get; set; } = null!;
[Column(TypeName = "jsonb")] public PaymentDetails PaymentDetails { get; set; } = null!;
public decimal BasePrice { get; set; }
public Guid? CouponId { get; set; }
public Coupon? Coupon { get; set; }
public Instant? RenewalAt { get; set; }
public Guid AccountId { get; set; }
public Account.Account Account { get; set; } = null!;
[NotMapped]
public bool IsAvailable
{
get
{
if (!IsActive) return false;
var now = SystemClock.Instance.GetCurrentInstant();
if (BegunAt > now) return false;
if (EndedAt.HasValue && now > EndedAt.Value) return false;
if (RenewalAt.HasValue && now > RenewalAt.Value) return false;
if (Status != SubscriptionStatus.Active) return false;
return true;
}
}
[NotMapped]
public decimal FinalPrice
{
get
{
if (IsFreeTrial) return 0;
if (Coupon == null) return BasePrice;
var now = SystemClock.Instance.GetCurrentInstant();
if (Coupon.AffectedAt.HasValue && now < Coupon.AffectedAt.Value ||
Coupon.ExpiredAt.HasValue && now > Coupon.ExpiredAt.Value) return BasePrice;
if (Coupon.DiscountAmount.HasValue) return BasePrice - Coupon.DiscountAmount.Value;
if (Coupon.DiscountRate.HasValue) return BasePrice * (decimal)(1 - Coupon.DiscountRate.Value);
return BasePrice;
}
}
/// <summary>
/// Returns a reference object that contains a subset of subscription data
/// suitable for client-side use, with sensitive information removed.
/// </summary>
public SubscriptionReferenceObject ToReference()
{
return new SubscriptionReferenceObject
{
Id = Id,
Identifier = Identifier,
BegunAt = BegunAt,
EndedAt = EndedAt,
IsActive = IsActive,
IsAvailable = IsAvailable,
IsFreeTrial = IsFreeTrial,
Status = Status,
BasePrice = BasePrice,
FinalPrice = FinalPrice,
RenewalAt = RenewalAt,
AccountId = AccountId
};
}
public Shared.Proto.Subscription ToProtoValue() => new()
{
Id = Id.ToString(),
BegunAt = BegunAt.ToTimestamp(),
EndedAt = EndedAt?.ToTimestamp(),
Identifier = Identifier,
IsActive = IsActive,
IsFreeTrial = IsFreeTrial,
Status = (Shared.Proto.SubscriptionStatus)Status,
PaymentMethod = PaymentMethod,
PaymentDetails = PaymentDetails.ToProtoValue(),
BasePrice = BasePrice.ToString(CultureInfo.InvariantCulture),
CouponId = CouponId?.ToString(),
Coupon = Coupon?.ToProtoValue(),
RenewalAt = RenewalAt?.ToTimestamp(),
AccountId = AccountId.ToString(),
IsAvailable = IsAvailable,
FinalPrice = FinalPrice.ToString(CultureInfo.InvariantCulture),
CreatedAt = CreatedAt.ToTimestamp(),
UpdatedAt = UpdatedAt.ToTimestamp()
};
public static Subscription FromProtoValue(Shared.Proto.Subscription proto) => new()
{
Id = Guid.Parse(proto.Id),
BegunAt = proto.BegunAt.ToInstant(),
EndedAt = proto.EndedAt?.ToInstant(),
Identifier = proto.Identifier,
IsActive = proto.IsActive,
IsFreeTrial = proto.IsFreeTrial,
Status = (SubscriptionStatus)proto.Status,
PaymentMethod = proto.PaymentMethod,
PaymentDetails = PaymentDetails.FromProtoValue(proto.PaymentDetails),
BasePrice = decimal.Parse(proto.BasePrice),
CouponId = proto.HasCouponId ? Guid.Parse(proto.CouponId) : null,
Coupon = proto.Coupon is not null ? Coupon.FromProtoValue(proto.Coupon) : null,
RenewalAt = proto.RenewalAt?.ToInstant(),
AccountId = Guid.Parse(proto.AccountId),
CreatedAt = proto.CreatedAt.ToInstant(),
UpdatedAt = proto.UpdatedAt.ToInstant()
};
}
/// <summary>
/// A reference object for Subscription that contains only non-sensitive information
/// suitable for client-side use.
/// </summary>
public class SubscriptionReferenceObject : ModelBase
{
public Guid Id { get; set; }
public string Identifier { get; set; } = null!;
public Instant BegunAt { get; set; }
public Instant? EndedAt { get; set; }
public bool IsActive { get; set; }
public bool IsAvailable { get; set; }
public bool IsFreeTrial { get; set; }
public SubscriptionStatus Status { get; set; }
public decimal BasePrice { get; set; }
public decimal FinalPrice { get; set; }
public Instant? RenewalAt { get; set; }
public Guid AccountId { get; set; }
/// <summary>
/// Gets the human-readable name of the subscription type if available.
/// </summary>
[NotMapped]
public string? DisplayName => SubscriptionTypeData.SubscriptionHumanReadable.TryGetValue(Identifier, out var name)
? name
: null;
public Shared.Proto.SubscriptionReferenceObject ToProtoValue() => new()
{
Id = Id.ToString(),
Identifier = Identifier,
BegunAt = BegunAt.ToTimestamp(),
EndedAt = EndedAt?.ToTimestamp(),
IsActive = IsActive,
IsAvailable = IsAvailable,
IsFreeTrial = IsFreeTrial,
Status = (Shared.Proto.SubscriptionStatus)Status,
BasePrice = BasePrice.ToString(CultureInfo.CurrentCulture),
FinalPrice = FinalPrice.ToString(CultureInfo.CurrentCulture),
RenewalAt = RenewalAt?.ToTimestamp(),
AccountId = AccountId.ToString(),
DisplayName = DisplayName,
CreatedAt = CreatedAt.ToTimestamp(),
UpdatedAt = UpdatedAt.ToTimestamp()
};
public static SubscriptionReferenceObject FromProtoValue(Shared.Proto.SubscriptionReferenceObject proto) => new()
{
Id = Guid.Parse(proto.Id),
Identifier = proto.Identifier,
BegunAt = proto.BegunAt.ToInstant(),
EndedAt = proto.EndedAt?.ToInstant(),
IsActive = proto.IsActive,
IsAvailable = proto.IsAvailable,
IsFreeTrial = proto.IsFreeTrial,
Status = (SubscriptionStatus)proto.Status,
BasePrice = decimal.Parse(proto.BasePrice),
FinalPrice = decimal.Parse(proto.FinalPrice),
RenewalAt = proto.RenewalAt?.ToInstant(),
AccountId = Guid.Parse(proto.AccountId),
CreatedAt = proto.CreatedAt.ToInstant(),
UpdatedAt = proto.UpdatedAt.ToInstant()
};
}
public class PaymentDetails
{
public string Currency { get; set; } = null!;
public string? OrderId { get; set; }
public Shared.Proto.PaymentDetails ToProtoValue() => new()
{
Currency = Currency,
OrderId = OrderId,
};
public static PaymentDetails FromProtoValue(Shared.Proto.PaymentDetails proto) => new()
{
Currency = proto.Currency,
OrderId = proto.OrderId,
};
}
/// <summary>
/// A discount that can applies in purchases among the Solar Network.
/// For now, it can be used in the subscription purchase.
/// </summary>
public class Coupon : ModelBase
{
public Guid Id { get; set; } = Guid.NewGuid();
/// <summary>
/// The items that can apply this coupon.
/// Leave it to null to apply to all items.
/// </summary>
[MaxLength(4096)]
public string? Identifier { get; set; }
/// <summary>
/// The code that human-readable and memorizable.
/// Leave it blank to use it only with the ID.
/// </summary>
[MaxLength(1024)]
public string? Code { get; set; }
public Instant? AffectedAt { get; set; }
public Instant? ExpiredAt { get; set; }
/// <summary>
/// The amount of the discount.
/// If this field and the rate field are both not null,
/// the amount discount will be applied and the discount rate will be ignored.
/// Formula: <code>final price = base price - discount amount</code>
/// </summary>
public decimal? DiscountAmount { get; set; }
/// <summary>
/// The percentage of the discount.
/// If this field and the amount field are both not null,
/// this field will be ignored.
/// Formula: <code>final price = base price * (1 - discount rate)</code>
/// </summary>
public double? DiscountRate { get; set; }
/// <summary>
/// The max usage of the current coupon.
/// Leave it to null to use it unlimited.
/// </summary>
public int? MaxUsage { get; set; }
public Shared.Proto.Coupon ToProtoValue() => new()
{
Id = Id.ToString(),
Identifier = Identifier,
Code = Code,
AffectedAt = AffectedAt?.ToTimestamp(),
ExpiredAt = ExpiredAt?.ToTimestamp(),
DiscountAmount = DiscountAmount?.ToString(),
DiscountRate = DiscountRate,
MaxUsage = MaxUsage,
CreatedAt = CreatedAt.ToTimestamp(),
UpdatedAt = UpdatedAt.ToTimestamp()
};
public static Coupon FromProtoValue(Shared.Proto.Coupon proto) => new()
{
Id = Guid.Parse(proto.Id),
Identifier = proto.Identifier,
Code = proto.Code,
AffectedAt = proto.AffectedAt?.ToInstant(),
ExpiredAt = proto.ExpiredAt?.ToInstant(),
DiscountAmount = proto.HasDiscountAmount ? decimal.Parse(proto.DiscountAmount) : null,
DiscountRate = proto.DiscountRate,
MaxUsage = proto.MaxUsage,
CreatedAt = proto.CreatedAt.ToInstant(),
UpdatedAt = proto.UpdatedAt.ToInstant()
};
}

View File

@@ -4,6 +4,7 @@ using Microsoft.EntityFrameworkCore;
using NodaTime;
using System.ComponentModel.DataAnnotations;
using DysonNetwork.Pass.Wallet.PaymentHandlers;
using DysonNetwork.Shared.Models;
namespace DysonNetwork.Pass.Wallet;
@@ -14,7 +15,7 @@ public class SubscriptionController(SubscriptionService subscriptions, AfdianPay
{
[HttpGet]
[Authorize]
public async Task<ActionResult<List<Subscription>>> ListSubscriptions(
public async Task<ActionResult<List<SnSubscription>>> ListSubscriptions(
[FromQuery] int offset = 0,
[FromQuery] int take = 20
)
@@ -40,7 +41,7 @@ public class SubscriptionController(SubscriptionService subscriptions, AfdianPay
[HttpGet("fuzzy/{prefix}")]
[Authorize]
public async Task<ActionResult<Subscription>> GetSubscriptionFuzzy(string prefix)
public async Task<ActionResult<SnSubscription>> GetSubscriptionFuzzy(string prefix)
{
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
@@ -56,7 +57,7 @@ public class SubscriptionController(SubscriptionService subscriptions, AfdianPay
[HttpGet("{identifier}")]
[Authorize]
public async Task<ActionResult<Subscription>> GetSubscription(string identifier)
public async Task<ActionResult<SnSubscription>> GetSubscription(string identifier)
{
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
@@ -79,7 +80,7 @@ public class SubscriptionController(SubscriptionService subscriptions, AfdianPay
[HttpPost]
[Authorize]
public async Task<ActionResult<Subscription>> CreateSubscription(
public async Task<ActionResult<SnSubscription>> CreateSubscription(
[FromBody] CreateSubscriptionRequest request,
[FromHeader(Name = "X-Noop")] bool noop = false
)
@@ -118,7 +119,7 @@ public class SubscriptionController(SubscriptionService subscriptions, AfdianPay
[HttpPost("{identifier}/cancel")]
[Authorize]
public async Task<ActionResult<Subscription>> CancelSubscription(string identifier)
public async Task<ActionResult<SnSubscription>> CancelSubscription(string identifier)
{
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
@@ -135,7 +136,7 @@ public class SubscriptionController(SubscriptionService subscriptions, AfdianPay
[HttpPost("{identifier}/order")]
[Authorize]
public async Task<ActionResult<Order>> CreateSubscriptionOrder(string identifier)
public async Task<ActionResult<SnWalletOrder>> CreateSubscriptionOrder(string identifier)
{
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();

View File

@@ -1,3 +1,4 @@
using DysonNetwork.Shared.Models;
using Microsoft.EntityFrameworkCore;
using NodaTime;
using Quartz;

View File

@@ -4,6 +4,7 @@ using System.Text.Json;
using DysonNetwork.Pass.Localization;
using DysonNetwork.Pass.Wallet.PaymentHandlers;
using DysonNetwork.Shared.Cache;
using DysonNetwork.Shared.Models;
using DysonNetwork.Shared.Proto;
using Google.Protobuf.WellKnownTypes;
using Microsoft.EntityFrameworkCore;

View File

@@ -1,66 +0,0 @@
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.Text.Json.Serialization;
using DysonNetwork.Shared.Data;
using NodaTime.Serialization.Protobuf;
namespace DysonNetwork.Pass.Wallet;
public class Wallet : ModelBase
{
public Guid Id { get; set; } = Guid.NewGuid();
public ICollection<WalletPocket> Pockets { get; set; } = new List<WalletPocket>();
public Guid AccountId { get; set; }
public Account.Account Account { get; set; } = null!;
public Shared.Proto.Wallet ToProtoValue()
{
var proto = new Shared.Proto.Wallet
{
Id = Id.ToString(),
AccountId = AccountId.ToString(),
};
foreach (var pocket in Pockets)
{
proto.Pockets.Add(pocket.ToProtoValue());
}
return proto;
}
public static Wallet FromProtoValue(Shared.Proto.Wallet proto) => new()
{
Id = Guid.Parse(proto.Id),
AccountId = Guid.Parse(proto.AccountId),
Pockets = proto.Pockets.Select(WalletPocket.FromProtoValue).ToList(),
};
}
public class WalletPocket : ModelBase
{
public Guid Id { get; set; } = Guid.NewGuid();
[MaxLength(128)] public string Currency { get; set; } = null!;
public decimal Amount { get; set; }
public Guid WalletId { get; set; }
[JsonIgnore] public Wallet Wallet { get; set; } = null!;
public Shared.Proto.WalletPocket ToProtoValue() => new()
{
Id = Id.ToString(),
Currency = Currency,
Amount = Amount.ToString(CultureInfo.CurrentCulture),
WalletId = WalletId.ToString(),
};
public static WalletPocket FromProtoValue(Shared.Proto.WalletPocket proto) => new()
{
Id = Guid.Parse(proto.Id),
Currency = proto.Currency,
Amount = decimal.Parse(proto.Amount),
WalletId = Guid.Parse(proto.WalletId),
};
}

View File

@@ -1,5 +1,6 @@
using System.ComponentModel.DataAnnotations;
using DysonNetwork.Pass.Permission;
using DysonNetwork.Shared.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
@@ -40,7 +41,7 @@ public class WalletController(AppDatabase db, WalletService ws, PaymentService p
[HttpGet("transactions")]
[Authorize]
public async Task<ActionResult<List<Transaction>>> GetTransactions(
public async Task<ActionResult<List<SnWalletTransaction>>> GetTransactions(
[FromQuery] int offset = 0, [FromQuery] int take = 20
)
{
@@ -67,7 +68,7 @@ public class WalletController(AppDatabase db, WalletService ws, PaymentService p
[HttpGet("orders")]
[Authorize]
public async Task<ActionResult<List<Order>>> GetOrders(
public async Task<ActionResult<List<SnWalletOrder>>> GetOrders(
[FromQuery] int offset = 0, [FromQuery] int take = 20
)
{
@@ -104,7 +105,7 @@ public class WalletController(AppDatabase db, WalletService ws, PaymentService p
[HttpPost("balance")]
[Authorize]
[RequiredPermission("maintenance", "wallets.balance.modify")]
public async Task<ActionResult<Transaction>> ModifyWalletBalance([FromBody] WalletBalanceRequest request)
public async Task<ActionResult<SnWalletTransaction>> ModifyWalletBalance([FromBody] WalletBalanceRequest request)
{
var wallet = await ws.GetWalletAsync(request.AccountId);
if (wallet is null) return NotFound("Wallet was not found.");

View File

@@ -1,3 +1,4 @@
using DysonNetwork.Shared.Models;
using Microsoft.EntityFrameworkCore;
namespace DysonNetwork.Pass.Wallet;
@@ -27,7 +28,7 @@ public class WalletService(AppDatabase db)
return wallet;
}
public async Task<(WalletPocket wallet, bool isNewlyCreated)> GetOrCreateWalletPocketAsync(
public async Task<(SnWalletPocket wallet, bool isNewlyCreated)> GetOrCreateWalletPocketAsync(
Guid walletId,
string currency,
decimal? initialAmount = null
@@ -36,7 +37,7 @@ public class WalletService(AppDatabase db)
var pocket = await db.WalletPockets.FirstOrDefaultAsync(p => p.Currency == currency && p.WalletId == walletId);
if (pocket != null) return (pocket, false);
pocket = new WalletPocket
pocket = new SnWalletPocket
{
Currency = currency,
Amount = initialAmount ?? 0,