diff --git a/DysonNetwork.Pass/Account/AccountCurrentController.cs b/DysonNetwork.Pass/Account/AccountCurrentController.cs index a7aa84a..8ea02ed 100644 --- a/DysonNetwork.Pass/Account/AccountCurrentController.cs +++ b/DysonNetwork.Pass/Account/AccountCurrentController.cs @@ -597,7 +597,6 @@ public class AccountCurrentController( var query = db.AuthSessions .Include(session => session.Account) - .Include(session => session.Challenge) .Where(session => session.Account.Id == currentUser.Id); var total = await query.CountAsync(); diff --git a/DysonNetwork.Pass/Auth/Auth.cs b/DysonNetwork.Pass/Auth/Auth.cs index d182c73..6630978 100644 --- a/DysonNetwork.Pass/Auth/Auth.cs +++ b/DysonNetwork.Pass/Auth/Auth.cs @@ -70,7 +70,7 @@ public class DysonTokenAuthHandler( }; // Add scopes as claims - session.Challenge?.Scopes.ForEach(scope => claims.Add(new Claim("scope", scope))); + session.Scopes.ForEach(scope => claims.Add(new Claim("scope", scope))); // Add superuser claim if applicable if (session.Account.IsSuperuser) @@ -117,16 +117,17 @@ public class DysonTokenAuthHandler( { if (authHeader.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase)) { - var token = authHeader["Bearer ".Length..].Trim(); - var parts = token.Split('.'); + var tokenText = authHeader["Bearer ".Length..].Trim(); + var parts = tokenText.Split('.'); return new TokenInfo { - Token = token, + Token = tokenText, Type = parts.Length == 3 ? TokenType.OidcKey : TokenType.AuthKey }; } - else if (authHeader.StartsWith("AtField ", StringComparison.OrdinalIgnoreCase)) + + if (authHeader.StartsWith("AtField ", StringComparison.OrdinalIgnoreCase)) { return new TokenInfo { @@ -134,7 +135,8 @@ public class DysonTokenAuthHandler( Type = TokenType.AuthKey }; } - else if (authHeader.StartsWith("AkField ", StringComparison.OrdinalIgnoreCase)) + + if (authHeader.StartsWith("AkField ", StringComparison.OrdinalIgnoreCase)) { return new TokenInfo { diff --git a/DysonNetwork.Pass/Auth/AuthController.cs b/DysonNetwork.Pass/Auth/AuthController.cs index a321732..2edafec 100644 --- a/DysonNetwork.Pass/Auth/AuthController.cs +++ b/DysonNetwork.Pass/Auth/AuthController.cs @@ -34,8 +34,8 @@ public class AuthController( [Required] [MaxLength(256)] public string Account { get; set; } = null!; [Required] [MaxLength(512)] public string DeviceId { get; set; } = null!; [MaxLength(1024)] public string? DeviceName { get; set; } - public List Audiences { get; set; } = new(); - public List Scopes { get; set; } = new(); + public List Audiences { get; set; } = []; + public List Scopes { get; set; } = []; } [HttpPost("challenge")] @@ -68,15 +68,9 @@ public class AuthController( .Where(e => e.UserAgent == userAgent) .Where(e => e.StepRemain > 0) .Where(e => e.ExpiredAt != null && now < e.ExpiredAt) - .Where(e => e.Type == Shared.Models.ChallengeType.Login) .Where(e => e.DeviceId == request.DeviceId) .FirstOrDefaultAsync(); - if (existingChallenge is not null) - { - var existingSession = await db.AuthSessions.Where(e => e.ChallengeId == existingChallenge.Id) - .FirstOrDefaultAsync(); - if (existingSession is null) return existingChallenge; - } + if (existingChallenge is not null) return existingChallenge; var challenge = new SnAuthChallenge { @@ -111,14 +105,11 @@ public class AuthController( .ThenInclude(e => e.Profile) .FirstOrDefaultAsync(e => e.Id == id); - if (challenge is null) - { - logger.LogWarning("GetChallenge: challenge not found (challengeId={ChallengeId}, ip={IpAddress})", - id, HttpContext.Connection.RemoteIpAddress?.ToString()); - return NotFound("Auth challenge was not found."); - } + if (challenge is not null) return challenge; + logger.LogWarning("GetChallenge: challenge not found (challengeId={ChallengeId}, ip={IpAddress})", + id, HttpContext.Connection.RemoteIpAddress?.ToString()); + return NotFound("Auth challenge was not found."); - return challenge; } [HttpGet("challenge/{id:guid}/factors")] @@ -216,7 +207,7 @@ public class AuthController( throw new ArgumentException("Invalid password."); } } - catch (Exception ex) + catch (Exception) { challenge.FailedAttempts++; db.Update(challenge); @@ -229,8 +220,11 @@ public class AuthController( ); await db.SaveChangesAsync(); - logger.LogWarning("DoChallenge: authentication failure (challengeId={ChallengeId}, factorId={FactorId}, accountId={AccountId}, failedAttempts={FailedAttempts}, factorType={FactorType}, ip={IpAddress}, uaLength={UaLength})", - challenge.Id, factor.Id, challenge.AccountId, challenge.FailedAttempts, factor.Type, HttpContext.Connection.RemoteIpAddress?.ToString(), (HttpContext.Request.Headers.UserAgent.ToString() ?? "").Length); + logger.LogWarning( + "DoChallenge: authentication failure (challengeId={ChallengeId}, factorId={FactorId}, accountId={AccountId}, failedAttempts={FailedAttempts}, factorType={FactorType}, ip={IpAddress}, uaLength={UaLength})", + challenge.Id, factor.Id, challenge.AccountId, challenge.FailedAttempts, factor.Type, + HttpContext.Connection.RemoteIpAddress?.ToString(), + HttpContext.Request.Headers.UserAgent.ToString().Length); return BadRequest("Invalid password."); } @@ -240,7 +234,7 @@ public class AuthController( AccountService.SetCultureInfo(challenge.Account); await pusher.SendPushNotificationToUserAsync(new SendPushNotificationToUserRequest { - Notification = new PushNotification() + Notification = new PushNotification { Topic = "auth.login", Title = localizer["NewLoginTitle"], @@ -279,7 +273,7 @@ public class AuthController( { [Required] [MaxLength(512)] public string DeviceId { get; set; } = null!; [MaxLength(1024)] public string? DeviceName { get; set; } - [Required] public DysonNetwork.Shared.Models.ClientPlatform Platform { get; set; } + [Required] public Shared.Models.ClientPlatform Platform { get; set; } public Instant? ExpiredAt { get; set; } } @@ -338,8 +332,9 @@ public class AuthController( [Microsoft.AspNetCore.Authorization.Authorize] // Use full namespace to avoid ambiguity with DysonNetwork.Pass.Permission.Authorize public async Task> LoginFromSession([FromBody] NewSessionRequest request) { - if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser || - HttpContext.Items["CurrentSession"] is not Shared.Models.SnAuthSession currentSession) return Unauthorized(); + if (HttpContext.Items["CurrentUser"] is not SnAccount || + HttpContext.Items["CurrentSession"] is not SnAuthSession currentSession) + return Unauthorized(); var newSession = await auth.CreateSessionFromParentAsync( currentSession, @@ -352,16 +347,15 @@ public class AuthController( var tk = auth.CreateToken(newSession); // Set cookie using HttpContext, similar to CreateSessionAndIssueToken - var cookieDomain = _cookieDomain; HttpContext.Response.Cookies.Append(AuthConstants.CookieTokenName, tk, new CookieOptions { HttpOnly = true, Secure = true, SameSite = SameSiteMode.Lax, - Domain = cookieDomain, + Domain = _cookieDomain, Expires = request.ExpiredAt?.ToDateTimeOffset() ?? DateTime.UtcNow.AddYears(20) }); return Ok(new TokenExchangeResponse { Token = tk }); } -} +} \ No newline at end of file diff --git a/DysonNetwork.Pass/Auth/AuthService.cs b/DysonNetwork.Pass/Auth/AuthService.cs index 9497ded..c2a1cd1 100644 --- a/DysonNetwork.Pass/Auth/AuthService.cs +++ b/DysonNetwork.Pass/Auth/AuthService.cs @@ -31,7 +31,7 @@ public class AuthService( { // 1) Find out how many authentication factors the account has enabled. var enabledFactors = await db.AccountAuthFactors - .Where(f => f.AccountId == account.Id) + .Where(f => f.AccountId == account.Id && f.Type != AccountAuthFactorType.PinCode) .Where(f => f.EnabledAt != null) .ToListAsync(); var maxSteps = enabledFactors.Count; @@ -42,13 +42,15 @@ public class AuthService( // 2) Get login context from recent sessions var recentSessions = await db.AuthSessions - .Include(s => s.Challenge) .Where(s => s.AccountId == account.Id) .Where(s => s.LastGrantedAt != null) .OrderByDescending(s => s.LastGrantedAt) .Take(10) .ToListAsync(); + var recentChallengeIds = recentSessions.Where(s => s.ChallengeId != null).Select(s => s.ChallengeId.Value).ToList(); + var recentChallenges = await db.AuthChallenges.Where(c => recentChallengeIds.Contains(c.Id)).ToListAsync(); + var ipAddress = request.HttpContext.Connection.RemoteIpAddress?.ToString(); var userAgent = request.Headers.UserAgent.ToString(); @@ -60,14 +62,14 @@ public class AuthService( else { // Check if IP has been used before - var ipPreviouslyUsed = recentSessions.Any(s => s.Challenge?.IpAddress == ipAddress); + var ipPreviouslyUsed = recentChallenges.Any(c => c.IpAddress == ipAddress); if (!ipPreviouslyUsed) { riskScore += 8; } // Check geographical distance for last known location - var lastKnownIp = recentSessions.FirstOrDefault(s => !string.IsNullOrWhiteSpace(s.Challenge?.IpAddress))?.Challenge?.IpAddress; + var lastKnownIp = recentChallenges.FirstOrDefault(c => !string.IsNullOrWhiteSpace(c.IpAddress))?.IpAddress; if (!string.IsNullOrWhiteSpace(lastKnownIp) && lastKnownIp != ipAddress) { riskScore += 6; @@ -81,9 +83,9 @@ public class AuthService( } else { - var uaPreviouslyUsed = recentSessions.Any(s => - !string.IsNullOrWhiteSpace(s.Challenge?.UserAgent) && - string.Equals(s.Challenge.UserAgent, userAgent, StringComparison.OrdinalIgnoreCase)); + var uaPreviouslyUsed = recentChallenges.Any(c => + !string.IsNullOrWhiteSpace(c.UserAgent) && + string.Equals(c.UserAgent, userAgent, StringComparison.OrdinalIgnoreCase)); if (!uaPreviouslyUsed) { @@ -184,30 +186,18 @@ public class AuthService( public async Task CreateSessionForOidcAsync(SnAccount account, Instant time, Guid? customAppId = null, SnAuthSession? parentSession = null) { - var challenge = new SnAuthChallenge - { - AccountId = account.Id, - IpAddress = HttpContext.Connection.RemoteIpAddress?.ToString(), - UserAgent = HttpContext.Request.Headers.UserAgent, - StepRemain = 1, - StepTotal = 1, - Type = customAppId is not null ? ChallengeType.OAuth : ChallengeType.Oidc, - DeviceId = Guid.NewGuid().ToString(), - DeviceName = "OIDC/OAuth", - Platform = ClientPlatform.Web, - }; - var session = new SnAuthSession { AccountId = account.Id, CreatedAt = time, LastGrantedAt = time, - Challenge = challenge, + IpAddress = HttpContext.Connection.RemoteIpAddress?.ToString(), + UserAgent = HttpContext.Request.Headers.UserAgent, AppId = customAppId, - ParentSessionId = parentSession?.Id + ParentSessionId = parentSession?.Id, + Type = customAppId is not null ? SessionType.OAuth : SessionType.Oidc, }; - db.AuthChallenges.Add(challenge); db.AuthSessions.Add(session); await db.SaveChangesAsync(); @@ -419,20 +409,19 @@ public class AuthService( if (challenge.StepRemain != 0) throw new ArgumentException("Challenge not yet completed."); - var hasSession = await db.AuthSessions - .AnyAsync(e => e.ChallengeId == challenge.Id); - if (hasSession) - throw new ArgumentException("Session already exists for this challenge."); - var device = await GetOrCreateDeviceAsync(challenge.AccountId, challenge.DeviceId, challenge.DeviceName, challenge.Platform); - + var now = SystemClock.Instance.GetCurrentInstant(); var session = new SnAuthSession { LastGrantedAt = now, ExpiredAt = now.Plus(Duration.FromDays(7)), AccountId = challenge.AccountId, + IpAddress = challenge.IpAddress, + UserAgent = challenge.UserAgent, + Scopes = challenge.Scopes, + Audiences = challenge.Audiences, ChallengeId = challenge.Id, ClientId = device.Id, }; @@ -457,7 +446,7 @@ public class AuthService( return tk; } - private string CreateCompactToken(Guid sessionId, RSA rsa) + private static string CreateCompactToken(Guid sessionId, RSA rsa) { // Create the payload: just the session ID var payloadBytes = sessionId.ToByteArray(); diff --git a/DysonNetwork.Pass/Auth/OidcProvider/Controllers/OidcProviderController.cs b/DysonNetwork.Pass/Auth/OidcProvider/Controllers/OidcProviderController.cs index f356dd6..75ae9e5 100644 --- a/DysonNetwork.Pass/Auth/OidcProvider/Controllers/OidcProviderController.cs +++ b/DysonNetwork.Pass/Auth/OidcProvider/Controllers/OidcProviderController.cs @@ -306,7 +306,7 @@ public class OidcProviderController( HttpContext.Items["CurrentSession"] is not SnAuthSession currentSession) return Unauthorized(); // Get requested scopes from the token - var scopes = currentSession.Challenge?.Scopes ?? []; + var scopes = currentSession.Scopes; var userInfo = new Dictionary { diff --git a/DysonNetwork.Pass/Auth/OidcProvider/Services/OidcProviderService.cs b/DysonNetwork.Pass/Auth/OidcProvider/Services/OidcProviderService.cs index a92f667..bcbeebe 100644 --- a/DysonNetwork.Pass/Auth/OidcProvider/Services/OidcProviderService.cs +++ b/DysonNetwork.Pass/Auth/OidcProvider/Services/OidcProviderService.cs @@ -72,7 +72,6 @@ public class OidcProviderService( var now = SystemClock.Instance.GetCurrentInstant(); var queryable = db.AuthSessions - .Include(s => s.Challenge) .AsQueryable(); if (withAccount) queryable = queryable @@ -85,8 +84,7 @@ public class OidcProviderService( .Where(s => s.AccountId == accountId && s.AppId == clientId && (s.ExpiredAt == null || s.ExpiredAt > now) && - s.Challenge != null && - s.Challenge.Type == Shared.Models.ChallengeType.OAuth) + s.Type == Shared.Models.SessionType.OAuth) .OrderByDescending(s => s.CreatedAt) .FirstOrDefaultAsync(); } @@ -511,7 +509,6 @@ public class OidcProviderService( { return await db.AuthSessions .Include(s => s.Account) - .Include(s => s.Challenge) .FirstOrDefaultAsync(s => s.Id == sessionId); } diff --git a/DysonNetwork.Pass/Auth/TokenAuthService.cs b/DysonNetwork.Pass/Auth/TokenAuthService.cs index 6dbc8a7..dc99563 100644 --- a/DysonNetwork.Pass/Auth/TokenAuthService.cs +++ b/DysonNetwork.Pass/Auth/TokenAuthService.cs @@ -77,7 +77,7 @@ public class TokenAuthService( "AuthenticateTokenAsync: success via cache (sessionId={SessionId}, accountId={AccountId}, scopes={ScopeCount}, expiresAt={ExpiresAt})", sessionId, session.AccountId, - session.Challenge?.Scopes.Count, + session.Scopes.Count, session.ExpiredAt ); return (true, session, null); @@ -87,7 +87,6 @@ public class TokenAuthService( session = await db.AuthSessions .AsNoTracking() - .Include(e => e.Challenge) .Include(e => e.Client) .Include(e => e.Account) .ThenInclude(e => e.Profile) @@ -112,9 +111,9 @@ public class TokenAuthService( session.AccountId, session.ClientId, session.AppId, - session.Challenge?.Scopes.Count, - session.Challenge?.IpAddress, - (session.Challenge?.UserAgent ?? string.Empty).Length + session.Scopes.Count, + session.IpAddress, + (session.UserAgent ?? string.Empty).Length ); logger.LogDebug("AuthenticateTokenAsync: enriching account with subscription (accountId={AccountId})", session.AccountId); diff --git a/DysonNetwork.Pass/Migrations/20251202160759_SimplifiedAuthSession.Designer.cs b/DysonNetwork.Pass/Migrations/20251202160759_SimplifiedAuthSession.Designer.cs new file mode 100644 index 0000000..fa5cc67 --- /dev/null +++ b/DysonNetwork.Pass/Migrations/20251202160759_SimplifiedAuthSession.Designer.cs @@ -0,0 +1,2882 @@ +// +using System; +using System.Collections.Generic; +using System.Text.Json; +using DysonNetwork.Pass; +using DysonNetwork.Shared.GeoIp; +using DysonNetwork.Shared.Models; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using NodaTime; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace DysonNetwork.Pass.Migrations +{ + [DbContext(typeof(AppDatabase))] + [Migration("20251202160759_SimplifiedAuthSession")] + partial class SimplifiedAuthSession + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.11") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnAbuseReport", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("AccountId") + .HasColumnType("uuid") + .HasColumnName("account_id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("Reason") + .IsRequired() + .HasMaxLength(8192) + .HasColumnType("character varying(8192)") + .HasColumnName("reason"); + + b.Property("Resolution") + .HasMaxLength(8192) + .HasColumnType("character varying(8192)") + .HasColumnName("resolution"); + + b.Property("ResolvedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("resolved_at"); + + b.Property("ResourceIdentifier") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("resource_identifier"); + + b.Property("Type") + .HasColumnType("integer") + .HasColumnName("type"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.HasKey("Id") + .HasName("pk_abuse_reports"); + + b.HasIndex("AccountId") + .HasDatabaseName("ix_abuse_reports_account_id"); + + b.ToTable("abuse_reports", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnAccount", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("ActivatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("activated_at"); + + b.Property("AutomatedId") + .HasColumnType("uuid") + .HasColumnName("automated_id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("IsSuperuser") + .HasColumnType("boolean") + .HasColumnName("is_superuser"); + + b.Property("Language") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("language"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("name"); + + b.Property("Nick") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("nick"); + + b.Property("Region") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("region"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.HasKey("Id") + .HasName("pk_accounts"); + + b.HasIndex("Name") + .IsUnique() + .HasDatabaseName("ix_accounts_name"); + + b.ToTable("accounts", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnAccountAuthFactor", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("AccountId") + .HasColumnType("uuid") + .HasColumnName("account_id"); + + b.Property>("Config") + .HasColumnType("jsonb") + .HasColumnName("config"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("EnabledAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("enabled_at"); + + b.Property("ExpiredAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("expired_at"); + + b.Property("Secret") + .HasMaxLength(8196) + .HasColumnType("character varying(8196)") + .HasColumnName("secret"); + + b.Property("Trustworthy") + .HasColumnType("integer") + .HasColumnName("trustworthy"); + + b.Property("Type") + .HasColumnType("integer") + .HasColumnName("type"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.HasKey("Id") + .HasName("pk_account_auth_factors"); + + b.HasIndex("AccountId") + .HasDatabaseName("ix_account_auth_factors_account_id"); + + b.ToTable("account_auth_factors", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnAccountBadge", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("AccountId") + .HasColumnType("uuid") + .HasColumnName("account_id"); + + b.Property("ActivatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("activated_at"); + + b.Property("Caption") + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("caption"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("ExpiredAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("expired_at"); + + b.Property("Label") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("label"); + + b.Property>("Meta") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("meta"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("type"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.HasKey("Id") + .HasName("pk_badges"); + + b.HasIndex("AccountId") + .HasDatabaseName("ix_badges_account_id"); + + b.ToTable("badges", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnAccountConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("AccessToken") + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("access_token"); + + b.Property("AccountId") + .HasColumnType("uuid") + .HasColumnName("account_id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("LastUsedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_used_at"); + + b.Property>("Meta") + .HasColumnType("jsonb") + .HasColumnName("meta"); + + b.Property("ProvidedIdentifier") + .IsRequired() + .HasMaxLength(8192) + .HasColumnType("character varying(8192)") + .HasColumnName("provided_identifier"); + + b.Property("Provider") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("provider"); + + b.Property("RefreshToken") + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("refresh_token"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.HasKey("Id") + .HasName("pk_account_connections"); + + b.HasIndex("AccountId") + .HasDatabaseName("ix_account_connections_account_id"); + + b.ToTable("account_connections", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnAccountContact", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("AccountId") + .HasColumnType("uuid") + .HasColumnName("account_id"); + + b.Property("Content") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("content"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("IsPrimary") + .HasColumnType("boolean") + .HasColumnName("is_primary"); + + b.Property("IsPublic") + .HasColumnType("boolean") + .HasColumnName("is_public"); + + b.Property("Type") + .HasColumnType("integer") + .HasColumnName("type"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.Property("VerifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("verified_at"); + + b.HasKey("Id") + .HasName("pk_account_contacts"); + + b.HasIndex("AccountId") + .HasDatabaseName("ix_account_contacts_account_id"); + + b.ToTable("account_contacts", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnAccountProfile", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("AccountId") + .HasColumnType("uuid") + .HasColumnName("account_id"); + + b.Property("ActiveBadge") + .HasColumnType("jsonb") + .HasColumnName("active_badge"); + + b.Property("Background") + .HasColumnType("jsonb") + .HasColumnName("background"); + + b.Property("Bio") + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("bio"); + + b.Property("Birthday") + .HasColumnType("timestamp with time zone") + .HasColumnName("birthday"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("Experience") + .HasColumnType("integer") + .HasColumnName("experience"); + + b.Property("FirstName") + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("first_name"); + + b.Property("Gender") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("gender"); + + b.Property("LastName") + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("last_name"); + + b.Property("LastSeenAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_seen_at"); + + b.Property>("Links") + .HasColumnType("jsonb") + .HasColumnName("links"); + + b.Property("Location") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("location"); + + b.Property("MiddleName") + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("middle_name"); + + b.Property("Picture") + .HasColumnType("jsonb") + .HasColumnName("picture"); + + b.Property("Pronouns") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("pronouns"); + + b.Property("SocialCredits") + .HasColumnType("double precision") + .HasColumnName("social_credits"); + + b.Property("TimeZone") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("time_zone"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.Property("UsernameColor") + .HasColumnType("jsonb") + .HasColumnName("username_color"); + + b.Property("Verification") + .HasColumnType("jsonb") + .HasColumnName("verification"); + + b.HasKey("Id") + .HasName("pk_account_profiles"); + + b.HasIndex("AccountId") + .IsUnique() + .HasDatabaseName("ix_account_profiles_account_id"); + + b.ToTable("account_profiles", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnAccountPunishment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("AccountId") + .HasColumnType("uuid") + .HasColumnName("account_id"); + + b.Property>("BlockedPermissions") + .HasColumnType("jsonb") + .HasColumnName("blocked_permissions"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("ExpiredAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("expired_at"); + + b.Property("Reason") + .IsRequired() + .HasMaxLength(8192) + .HasColumnType("character varying(8192)") + .HasColumnName("reason"); + + b.Property("Type") + .HasColumnType("integer") + .HasColumnName("type"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.HasKey("Id") + .HasName("pk_punishments"); + + b.HasIndex("AccountId") + .HasDatabaseName("ix_punishments_account_id"); + + b.ToTable("punishments", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnAccountRelationship", b => + { + b.Property("AccountId") + .HasColumnType("uuid") + .HasColumnName("account_id"); + + b.Property("RelatedId") + .HasColumnType("uuid") + .HasColumnName("related_id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("ExpiredAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("expired_at"); + + b.Property("Status") + .HasColumnType("smallint") + .HasColumnName("status"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.HasKey("AccountId", "RelatedId") + .HasName("pk_account_relationships"); + + b.HasIndex("RelatedId") + .HasDatabaseName("ix_account_relationships_related_id"); + + b.ToTable("account_relationships", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnAccountStatus", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("AccountId") + .HasColumnType("uuid") + .HasColumnName("account_id"); + + b.Property("AppIdentifier") + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("app_identifier"); + + b.Property("Attitude") + .HasColumnType("integer") + .HasColumnName("attitude"); + + b.Property("ClearedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("cleared_at"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("IsAutomated") + .HasColumnType("boolean") + .HasColumnName("is_automated"); + + b.Property("IsInvisible") + .HasColumnType("boolean") + .HasColumnName("is_invisible"); + + b.Property("IsNotDisturb") + .HasColumnType("boolean") + .HasColumnName("is_not_disturb"); + + b.Property("Label") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("label"); + + b.Property>("Meta") + .HasColumnType("jsonb") + .HasColumnName("meta"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.HasKey("Id") + .HasName("pk_account_statuses"); + + b.HasIndex("AccountId") + .HasDatabaseName("ix_account_statuses_account_id"); + + b.ToTable("account_statuses", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnActionLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("AccountId") + .HasColumnType("uuid") + .HasColumnName("account_id"); + + b.Property("Action") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("action"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("IpAddress") + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("ip_address"); + + b.Property("Location") + .HasColumnType("jsonb") + .HasColumnName("location"); + + b.Property>("Meta") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("meta"); + + b.Property("SessionId") + .HasColumnType("uuid") + .HasColumnName("session_id"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.Property("UserAgent") + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasColumnName("user_agent"); + + b.HasKey("Id") + .HasName("pk_action_logs"); + + b.HasIndex("AccountId") + .HasDatabaseName("ix_action_logs_account_id"); + + b.ToTable("action_logs", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnAffiliationResult", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("ResourceIdentifier") + .IsRequired() + .HasMaxLength(8192) + .HasColumnType("character varying(8192)") + .HasColumnName("resource_identifier"); + + b.Property("SpellId") + .HasColumnType("uuid") + .HasColumnName("spell_id"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.HasKey("Id") + .HasName("pk_affiliation_results"); + + b.HasIndex("SpellId") + .HasDatabaseName("ix_affiliation_results_spell_id"); + + b.ToTable("affiliation_results", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnAffiliationSpell", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("AccountId") + .HasColumnType("uuid") + .HasColumnName("account_id"); + + b.Property("AffectedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("affected_at"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("ExpiresAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("expires_at"); + + b.Property>("Meta") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("meta"); + + b.Property("Spell") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("spell"); + + b.Property("Type") + .HasColumnType("integer") + .HasColumnName("type"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.HasKey("Id") + .HasName("pk_affiliation_spells"); + + b.HasIndex("AccountId") + .HasDatabaseName("ix_affiliation_spells_account_id"); + + b.HasIndex("Spell") + .IsUnique() + .HasDatabaseName("ix_affiliation_spells_spell"); + + b.ToTable("affiliation_spells", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnApiKey", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("AccountId") + .HasColumnType("uuid") + .HasColumnName("account_id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("Label") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("label"); + + b.Property("SessionId") + .HasColumnType("uuid") + .HasColumnName("session_id"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.HasKey("Id") + .HasName("pk_api_keys"); + + b.HasIndex("AccountId") + .HasDatabaseName("ix_api_keys_account_id"); + + b.HasIndex("SessionId") + .HasDatabaseName("ix_api_keys_session_id"); + + b.ToTable("api_keys", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnAuthChallenge", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("AccountId") + .HasColumnType("uuid") + .HasColumnName("account_id"); + + b.Property>("Audiences") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("audiences"); + + b.Property>("BlacklistFactors") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("blacklist_factors"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeviceId") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasColumnName("device_id"); + + b.Property("DeviceName") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("device_name"); + + b.Property("ExpiredAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("expired_at"); + + b.Property("FailedAttempts") + .HasColumnType("integer") + .HasColumnName("failed_attempts"); + + b.Property("IpAddress") + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("ip_address"); + + b.Property("Location") + .HasColumnType("jsonb") + .HasColumnName("location"); + + b.Property("Nonce") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("nonce"); + + b.Property("Platform") + .HasColumnType("integer") + .HasColumnName("platform"); + + b.Property>("Scopes") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("scopes"); + + b.Property("StepRemain") + .HasColumnType("integer") + .HasColumnName("step_remain"); + + b.Property("StepTotal") + .HasColumnType("integer") + .HasColumnName("step_total"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.Property("UserAgent") + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasColumnName("user_agent"); + + b.HasKey("Id") + .HasName("pk_auth_challenges"); + + b.HasIndex("AccountId") + .HasDatabaseName("ix_auth_challenges_account_id"); + + b.ToTable("auth_challenges", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnAuthClient", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("AccountId") + .HasColumnType("uuid") + .HasColumnName("account_id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeviceId") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("device_id"); + + b.Property("DeviceLabel") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("device_label"); + + b.Property("DeviceName") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("device_name"); + + b.Property("Platform") + .HasColumnType("integer") + .HasColumnName("platform"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.HasKey("Id") + .HasName("pk_auth_clients"); + + b.HasIndex("AccountId") + .HasDatabaseName("ix_auth_clients_account_id"); + + b.ToTable("auth_clients", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnAuthSession", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("AccountId") + .HasColumnType("uuid") + .HasColumnName("account_id"); + + b.Property("AppId") + .HasColumnType("uuid") + .HasColumnName("app_id"); + + b.Property>("Audiences") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("audiences"); + + b.Property("ChallengeId") + .HasColumnType("uuid") + .HasColumnName("challenge_id"); + + b.Property("ClientId") + .HasColumnType("uuid") + .HasColumnName("client_id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("ExpiredAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("expired_at"); + + b.Property("IpAddress") + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("ip_address"); + + b.Property("LastGrantedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_granted_at"); + + b.Property("ParentSessionId") + .HasColumnType("uuid") + .HasColumnName("parent_session_id"); + + b.Property>("Scopes") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("scopes"); + + b.Property("Type") + .HasColumnType("integer") + .HasColumnName("type"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.Property("UserAgent") + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasColumnName("user_agent"); + + b.HasKey("Id") + .HasName("pk_auth_sessions"); + + b.HasIndex("AccountId") + .HasDatabaseName("ix_auth_sessions_account_id"); + + b.HasIndex("ClientId") + .HasDatabaseName("ix_auth_sessions_client_id"); + + b.HasIndex("ParentSessionId") + .HasDatabaseName("ix_auth_sessions_parent_session_id"); + + b.ToTable("auth_sessions", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnCheckInResult", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("AccountId") + .HasColumnType("uuid") + .HasColumnName("account_id"); + + b.Property("BackdatedFrom") + .HasColumnType("timestamp with time zone") + .HasColumnName("backdated_from"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("Level") + .HasColumnType("integer") + .HasColumnName("level"); + + b.Property("RewardExperience") + .HasColumnType("integer") + .HasColumnName("reward_experience"); + + b.Property("RewardPoints") + .HasColumnType("numeric") + .HasColumnName("reward_points"); + + b.Property>("Tips") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("tips"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.HasKey("Id") + .HasName("pk_account_check_in_results"); + + b.HasIndex("AccountId") + .HasDatabaseName("ix_account_check_in_results_account_id"); + + b.ToTable("account_check_in_results", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnExperienceRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("AccountId") + .HasColumnType("uuid") + .HasColumnName("account_id"); + + b.Property("BonusMultiplier") + .HasColumnType("double precision") + .HasColumnName("bonus_multiplier"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("Delta") + .HasColumnType("bigint") + .HasColumnName("delta"); + + b.Property("Reason") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("reason"); + + b.Property("ReasonType") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("reason_type"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.HasKey("Id") + .HasName("pk_experience_records"); + + b.HasIndex("AccountId") + .HasDatabaseName("ix_experience_records_account_id"); + + b.ToTable("experience_records", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnLottery", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("AccountId") + .HasColumnType("uuid") + .HasColumnName("account_id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DrawDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("draw_date"); + + b.Property("DrawStatus") + .HasColumnType("integer") + .HasColumnName("draw_status"); + + b.Property>("MatchedRegionOneNumbers") + .HasColumnType("jsonb") + .HasColumnName("matched_region_one_numbers"); + + b.Property("MatchedRegionTwoNumber") + .HasColumnType("integer") + .HasColumnName("matched_region_two_number"); + + b.Property("Multiplier") + .HasColumnType("integer") + .HasColumnName("multiplier"); + + b.Property>("RegionOneNumbers") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("region_one_numbers"); + + b.Property("RegionTwoNumber") + .HasColumnType("integer") + .HasColumnName("region_two_number"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.HasKey("Id") + .HasName("pk_lotteries"); + + b.HasIndex("AccountId") + .HasDatabaseName("ix_lotteries_account_id"); + + b.ToTable("lotteries", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnLotteryRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DrawDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("draw_date"); + + b.Property("TotalPrizeAmount") + .HasColumnType("bigint") + .HasColumnName("total_prize_amount"); + + b.Property("TotalPrizesAwarded") + .HasColumnType("integer") + .HasColumnName("total_prizes_awarded"); + + b.Property("TotalTickets") + .HasColumnType("integer") + .HasColumnName("total_tickets"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.Property>("WinningRegionOneNumbers") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("winning_region_one_numbers"); + + b.Property("WinningRegionTwoNumber") + .HasColumnType("integer") + .HasColumnName("winning_region_two_number"); + + b.HasKey("Id") + .HasName("pk_lottery_records"); + + b.ToTable("lottery_records", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnMagicSpell", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("AccountId") + .HasColumnType("uuid") + .HasColumnName("account_id"); + + b.Property("AffectedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("affected_at"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("ExpiresAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("expires_at"); + + b.Property>("Meta") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("meta"); + + b.Property("Spell") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("spell"); + + b.Property("Type") + .HasColumnType("integer") + .HasColumnName("type"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.HasKey("Id") + .HasName("pk_magic_spells"); + + b.HasIndex("AccountId") + .HasDatabaseName("ix_magic_spells_account_id"); + + b.HasIndex("Spell") + .IsUnique() + .HasDatabaseName("ix_magic_spells_spell"); + + b.ToTable("magic_spells", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnPermissionGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("key"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.HasKey("Id") + .HasName("pk_permission_groups"); + + b.ToTable("permission_groups", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnPermissionGroupMember", b => + { + b.Property("GroupId") + .HasColumnType("uuid") + .HasColumnName("group_id"); + + b.Property("Actor") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("actor"); + + b.Property("AffectedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("affected_at"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("ExpiredAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("expired_at"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.HasKey("GroupId", "Actor") + .HasName("pk_permission_group_members"); + + b.ToTable("permission_group_members", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnPermissionNode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Actor") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("actor"); + + b.Property("AffectedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("affected_at"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("ExpiredAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("expired_at"); + + b.Property("GroupId") + .HasColumnType("uuid") + .HasColumnName("group_id"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("key"); + + b.Property("Type") + .HasColumnType("integer") + .HasColumnName("type"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.Property("Value") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("value"); + + b.HasKey("Id") + .HasName("pk_permission_nodes"); + + b.HasIndex("GroupId") + .HasDatabaseName("ix_permission_nodes_group_id"); + + b.HasIndex("Key", "Actor") + .HasDatabaseName("ix_permission_nodes_key_actor"); + + b.ToTable("permission_nodes", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnPresenceActivity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("AccountId") + .HasColumnType("uuid") + .HasColumnName("account_id"); + + b.Property("Caption") + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("caption"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("LargeImage") + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("large_image"); + + b.Property("LeaseExpiresAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("lease_expires_at"); + + b.Property("LeaseMinutes") + .HasColumnType("integer") + .HasColumnName("lease_minutes"); + + b.Property("ManualId") + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("manual_id"); + + b.Property>("Meta") + .HasColumnType("jsonb") + .HasColumnName("meta"); + + b.Property("SmallImage") + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("small_image"); + + b.Property("Subtitle") + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("subtitle"); + + b.Property("SubtitleUrl") + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("subtitle_url"); + + b.Property("Title") + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("title"); + + b.Property("TitleUrl") + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("title_url"); + + b.Property("Type") + .HasColumnType("integer") + .HasColumnName("type"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.HasKey("Id") + .HasName("pk_presence_activities"); + + b.HasIndex("AccountId") + .HasDatabaseName("ix_presence_activities_account_id"); + + b.ToTable("presence_activities", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnRealm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("AccountId") + .HasColumnType("uuid") + .HasColumnName("account_id"); + + b.Property("Background") + .HasColumnType("jsonb") + .HasColumnName("background"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("description"); + + b.Property("IsCommunity") + .HasColumnType("boolean") + .HasColumnName("is_community"); + + b.Property("IsPublic") + .HasColumnType("boolean") + .HasColumnName("is_public"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("name"); + + b.Property("Picture") + .HasColumnType("jsonb") + .HasColumnName("picture"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("slug"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.Property("Verification") + .HasColumnType("jsonb") + .HasColumnName("verification"); + + b.HasKey("Id") + .HasName("pk_realms"); + + b.HasIndex("Slug") + .IsUnique() + .HasDatabaseName("ix_realms_slug"); + + b.ToTable("realms", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnRealmMember", b => + { + b.Property("RealmId") + .HasColumnType("uuid") + .HasColumnName("realm_id"); + + b.Property("AccountId") + .HasColumnType("uuid") + .HasColumnName("account_id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("JoinedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("joined_at"); + + b.Property("LeaveAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("leave_at"); + + b.Property("Role") + .HasColumnType("integer") + .HasColumnName("role"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.HasKey("RealmId", "AccountId") + .HasName("pk_realm_members"); + + b.ToTable("realm_members", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnSocialCreditRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("AccountId") + .HasColumnType("uuid") + .HasColumnName("account_id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("Delta") + .HasColumnType("double precision") + .HasColumnName("delta"); + + b.Property("ExpiredAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("expired_at"); + + b.Property("Reason") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("reason"); + + b.Property("ReasonType") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("reason_type"); + + b.Property("Status") + .HasColumnType("integer") + .HasColumnName("status"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.HasKey("Id") + .HasName("pk_social_credit_records"); + + b.HasIndex("AccountId") + .HasDatabaseName("ix_social_credit_records_account_id"); + + b.ToTable("social_credit_records", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnWallet", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("AccountId") + .HasColumnType("uuid") + .HasColumnName("account_id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.HasKey("Id") + .HasName("pk_wallets"); + + b.HasIndex("AccountId") + .HasDatabaseName("ix_wallets_account_id"); + + b.ToTable("wallets", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnWalletCoupon", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("AffectedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("affected_at"); + + b.Property("Code") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("code"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DiscountAmount") + .HasColumnType("numeric") + .HasColumnName("discount_amount"); + + b.Property("DiscountRate") + .HasColumnType("double precision") + .HasColumnName("discount_rate"); + + b.Property("ExpiredAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("expired_at"); + + b.Property("Identifier") + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("identifier"); + + b.Property("MaxUsage") + .HasColumnType("integer") + .HasColumnName("max_usage"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.HasKey("Id") + .HasName("pk_wallet_coupons"); + + b.ToTable("wallet_coupons", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnWalletFund", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("AmountOfSplits") + .HasColumnType("integer") + .HasColumnName("amount_of_splits"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatorAccountId") + .HasColumnType("uuid") + .HasColumnName("creator_account_id"); + + b.Property("Currency") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("currency"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("ExpiredAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("expired_at"); + + b.Property("IsOpen") + .HasColumnType("boolean") + .HasColumnName("is_open"); + + b.Property("Message") + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("message"); + + b.Property("RemainingAmount") + .HasColumnType("numeric") + .HasColumnName("remaining_amount"); + + b.Property("SplitType") + .HasColumnType("integer") + .HasColumnName("split_type"); + + b.Property("Status") + .HasColumnType("integer") + .HasColumnName("status"); + + b.Property("TotalAmount") + .HasColumnType("numeric") + .HasColumnName("total_amount"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.HasKey("Id") + .HasName("pk_wallet_funds"); + + b.HasIndex("CreatorAccountId") + .HasDatabaseName("ix_wallet_funds_creator_account_id"); + + b.ToTable("wallet_funds", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnWalletFundRecipient", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Amount") + .HasColumnType("numeric") + .HasColumnName("amount"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("FundId") + .HasColumnType("uuid") + .HasColumnName("fund_id"); + + b.Property("IsReceived") + .HasColumnType("boolean") + .HasColumnName("is_received"); + + b.Property("ReceivedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("received_at"); + + b.Property("RecipientAccountId") + .HasColumnType("uuid") + .HasColumnName("recipient_account_id"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.HasKey("Id") + .HasName("pk_wallet_fund_recipients"); + + b.HasIndex("FundId") + .HasDatabaseName("ix_wallet_fund_recipients_fund_id"); + + b.HasIndex("RecipientAccountId") + .HasDatabaseName("ix_wallet_fund_recipients_recipient_account_id"); + + b.ToTable("wallet_fund_recipients", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnWalletGift", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("BasePrice") + .HasColumnType("numeric") + .HasColumnName("base_price"); + + b.Property("CouponId") + .HasColumnType("uuid") + .HasColumnName("coupon_id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("ExpiresAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("expires_at"); + + b.Property("FinalPrice") + .HasColumnType("numeric") + .HasColumnName("final_price"); + + b.Property("GiftCode") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("gift_code"); + + b.Property("GifterId") + .HasColumnType("uuid") + .HasColumnName("gifter_id"); + + b.Property("IsOpenGift") + .HasColumnType("boolean") + .HasColumnName("is_open_gift"); + + b.Property("Message") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)") + .HasColumnName("message"); + + b.Property("PaymentDetails") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("payment_details"); + + b.Property("PaymentMethod") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("payment_method"); + + b.Property("RecipientId") + .HasColumnType("uuid") + .HasColumnName("recipient_id"); + + b.Property("RedeemedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("redeemed_at"); + + b.Property("RedeemerId") + .HasColumnType("uuid") + .HasColumnName("redeemer_id"); + + b.Property("Status") + .HasColumnType("integer") + .HasColumnName("status"); + + b.Property("SubscriptionId") + .HasColumnType("uuid") + .HasColumnName("subscription_id"); + + b.Property("SubscriptionIdentifier") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("subscription_identifier"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.HasKey("Id") + .HasName("pk_wallet_gifts"); + + b.HasIndex("CouponId") + .HasDatabaseName("ix_wallet_gifts_coupon_id"); + + b.HasIndex("GiftCode") + .HasDatabaseName("ix_wallet_gifts_gift_code"); + + b.HasIndex("GifterId") + .HasDatabaseName("ix_wallet_gifts_gifter_id"); + + b.HasIndex("RecipientId") + .HasDatabaseName("ix_wallet_gifts_recipient_id"); + + b.HasIndex("RedeemerId") + .HasDatabaseName("ix_wallet_gifts_redeemer_id"); + + b.HasIndex("SubscriptionId") + .IsUnique() + .HasDatabaseName("ix_wallet_gifts_subscription_id"); + + b.ToTable("wallet_gifts", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnWalletOrder", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Amount") + .HasColumnType("numeric") + .HasColumnName("amount"); + + b.Property("AppIdentifier") + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("app_identifier"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("Currency") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("currency"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("ExpiredAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("expired_at"); + + b.Property>("Meta") + .HasColumnType("jsonb") + .HasColumnName("meta"); + + b.Property("PayeeWalletId") + .HasColumnType("uuid") + .HasColumnName("payee_wallet_id"); + + b.Property("ProductIdentifier") + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("product_identifier"); + + b.Property("Remarks") + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("remarks"); + + b.Property("Status") + .HasColumnType("integer") + .HasColumnName("status"); + + b.Property("TransactionId") + .HasColumnType("uuid") + .HasColumnName("transaction_id"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.HasKey("Id") + .HasName("pk_payment_orders"); + + b.HasIndex("PayeeWalletId") + .HasDatabaseName("ix_payment_orders_payee_wallet_id"); + + b.HasIndex("TransactionId") + .HasDatabaseName("ix_payment_orders_transaction_id"); + + b.ToTable("payment_orders", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnWalletPocket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Amount") + .HasColumnType("numeric") + .HasColumnName("amount"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("Currency") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("currency"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.Property("WalletId") + .HasColumnType("uuid") + .HasColumnName("wallet_id"); + + b.HasKey("Id") + .HasName("pk_wallet_pockets"); + + b.HasIndex("WalletId") + .HasDatabaseName("ix_wallet_pockets_wallet_id"); + + b.ToTable("wallet_pockets", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnWalletSubscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("AccountId") + .HasColumnType("uuid") + .HasColumnName("account_id"); + + b.Property("BasePrice") + .HasColumnType("numeric") + .HasColumnName("base_price"); + + b.Property("BegunAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("begun_at"); + + b.Property("CouponId") + .HasColumnType("uuid") + .HasColumnName("coupon_id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("EndedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("ended_at"); + + b.Property("Identifier") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("identifier"); + + b.Property("IsActive") + .HasColumnType("boolean") + .HasColumnName("is_active"); + + b.Property("IsFreeTrial") + .HasColumnType("boolean") + .HasColumnName("is_free_trial"); + + b.Property("PaymentDetails") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("payment_details"); + + b.Property("PaymentMethod") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("payment_method"); + + b.Property("RenewalAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("renewal_at"); + + b.Property("Status") + .HasColumnType("integer") + .HasColumnName("status"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.HasKey("Id") + .HasName("pk_wallet_subscriptions"); + + b.HasIndex("AccountId") + .HasDatabaseName("ix_wallet_subscriptions_account_id"); + + b.HasIndex("CouponId") + .HasDatabaseName("ix_wallet_subscriptions_coupon_id"); + + b.HasIndex("Identifier") + .HasDatabaseName("ix_wallet_subscriptions_identifier"); + + b.HasIndex("Status") + .HasDatabaseName("ix_wallet_subscriptions_status"); + + b.HasIndex("AccountId", "Identifier") + .HasDatabaseName("ix_wallet_subscriptions_account_id_identifier"); + + b.HasIndex("AccountId", "IsActive") + .HasDatabaseName("ix_wallet_subscriptions_account_id_is_active"); + + b.ToTable("wallet_subscriptions", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnWalletTransaction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Amount") + .HasColumnType("numeric") + .HasColumnName("amount"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("Currency") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("currency"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("PayeeWalletId") + .HasColumnType("uuid") + .HasColumnName("payee_wallet_id"); + + b.Property("PayerWalletId") + .HasColumnType("uuid") + .HasColumnName("payer_wallet_id"); + + b.Property("Remarks") + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("remarks"); + + b.Property("Type") + .HasColumnType("integer") + .HasColumnName("type"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.HasKey("Id") + .HasName("pk_payment_transactions"); + + b.HasIndex("PayeeWalletId") + .HasDatabaseName("ix_payment_transactions_payee_wallet_id"); + + b.HasIndex("PayerWalletId") + .HasDatabaseName("ix_payment_transactions_payer_wallet_id"); + + b.ToTable("payment_transactions", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnAbuseReport", b => + { + b.HasOne("DysonNetwork.Shared.Models.SnAccount", "Account") + .WithMany() + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_abuse_reports_accounts_account_id"); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnAccountAuthFactor", b => + { + b.HasOne("DysonNetwork.Shared.Models.SnAccount", "Account") + .WithMany("AuthFactors") + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_account_auth_factors_accounts_account_id"); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnAccountBadge", b => + { + b.HasOne("DysonNetwork.Shared.Models.SnAccount", "Account") + .WithMany("Badges") + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_badges_accounts_account_id"); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnAccountConnection", b => + { + b.HasOne("DysonNetwork.Shared.Models.SnAccount", "Account") + .WithMany("Connections") + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_account_connections_accounts_account_id"); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnAccountContact", b => + { + b.HasOne("DysonNetwork.Shared.Models.SnAccount", "Account") + .WithMany("Contacts") + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_account_contacts_accounts_account_id"); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnAccountProfile", b => + { + b.HasOne("DysonNetwork.Shared.Models.SnAccount", "Account") + .WithOne("Profile") + .HasForeignKey("DysonNetwork.Shared.Models.SnAccountProfile", "AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_account_profiles_accounts_account_id"); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnAccountPunishment", b => + { + b.HasOne("DysonNetwork.Shared.Models.SnAccount", "Account") + .WithMany() + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_punishments_accounts_account_id"); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnAccountRelationship", b => + { + b.HasOne("DysonNetwork.Shared.Models.SnAccount", "Account") + .WithMany("OutgoingRelationships") + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_account_relationships_accounts_account_id"); + + b.HasOne("DysonNetwork.Shared.Models.SnAccount", "Related") + .WithMany("IncomingRelationships") + .HasForeignKey("RelatedId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_account_relationships_accounts_related_id"); + + b.Navigation("Account"); + + b.Navigation("Related"); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnAccountStatus", b => + { + b.HasOne("DysonNetwork.Shared.Models.SnAccount", "Account") + .WithMany() + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_account_statuses_accounts_account_id"); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnActionLog", b => + { + b.HasOne("DysonNetwork.Shared.Models.SnAccount", "Account") + .WithMany() + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_action_logs_accounts_account_id"); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnAffiliationResult", b => + { + b.HasOne("DysonNetwork.Shared.Models.SnAffiliationSpell", "Spell") + .WithMany() + .HasForeignKey("SpellId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_affiliation_results_affiliation_spells_spell_id"); + + b.Navigation("Spell"); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnAffiliationSpell", b => + { + b.HasOne("DysonNetwork.Shared.Models.SnAccount", "Account") + .WithMany() + .HasForeignKey("AccountId") + .HasConstraintName("fk_affiliation_spells_accounts_account_id"); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnApiKey", b => + { + b.HasOne("DysonNetwork.Shared.Models.SnAccount", "Account") + .WithMany() + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_api_keys_accounts_account_id"); + + b.HasOne("DysonNetwork.Shared.Models.SnAuthSession", "Session") + .WithMany() + .HasForeignKey("SessionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_api_keys_auth_sessions_session_id"); + + b.Navigation("Account"); + + b.Navigation("Session"); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnAuthChallenge", b => + { + b.HasOne("DysonNetwork.Shared.Models.SnAccount", "Account") + .WithMany("Challenges") + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_auth_challenges_accounts_account_id"); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnAuthClient", b => + { + b.HasOne("DysonNetwork.Shared.Models.SnAccount", "Account") + .WithMany() + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_auth_clients_accounts_account_id"); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnAuthSession", b => + { + b.HasOne("DysonNetwork.Shared.Models.SnAccount", "Account") + .WithMany("Sessions") + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_auth_sessions_accounts_account_id"); + + b.HasOne("DysonNetwork.Shared.Models.SnAuthClient", "Client") + .WithMany() + .HasForeignKey("ClientId") + .HasConstraintName("fk_auth_sessions_auth_clients_client_id"); + + b.HasOne("DysonNetwork.Shared.Models.SnAuthSession", "ParentSession") + .WithMany() + .HasForeignKey("ParentSessionId") + .HasConstraintName("fk_auth_sessions_auth_sessions_parent_session_id"); + + b.Navigation("Account"); + + b.Navigation("Client"); + + b.Navigation("ParentSession"); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnCheckInResult", b => + { + b.HasOne("DysonNetwork.Shared.Models.SnAccount", "Account") + .WithMany() + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_account_check_in_results_accounts_account_id"); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnExperienceRecord", b => + { + b.HasOne("DysonNetwork.Shared.Models.SnAccount", "Account") + .WithMany() + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_experience_records_accounts_account_id"); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnLottery", b => + { + b.HasOne("DysonNetwork.Shared.Models.SnAccount", "Account") + .WithMany() + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_lotteries_accounts_account_id"); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnMagicSpell", b => + { + b.HasOne("DysonNetwork.Shared.Models.SnAccount", "Account") + .WithMany() + .HasForeignKey("AccountId") + .HasConstraintName("fk_magic_spells_accounts_account_id"); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnPermissionGroupMember", b => + { + b.HasOne("DysonNetwork.Shared.Models.SnPermissionGroup", "Group") + .WithMany("Members") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_permission_group_members_permission_groups_group_id"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnPermissionNode", b => + { + b.HasOne("DysonNetwork.Shared.Models.SnPermissionGroup", "Group") + .WithMany("Nodes") + .HasForeignKey("GroupId") + .HasConstraintName("fk_permission_nodes_permission_groups_group_id"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnPresenceActivity", b => + { + b.HasOne("DysonNetwork.Shared.Models.SnAccount", "Account") + .WithMany() + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_presence_activities_accounts_account_id"); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnRealmMember", b => + { + b.HasOne("DysonNetwork.Shared.Models.SnRealm", "Realm") + .WithMany("Members") + .HasForeignKey("RealmId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_realm_members_realms_realm_id"); + + b.Navigation("Realm"); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnSocialCreditRecord", b => + { + b.HasOne("DysonNetwork.Shared.Models.SnAccount", "Account") + .WithMany() + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_social_credit_records_accounts_account_id"); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnWallet", b => + { + b.HasOne("DysonNetwork.Shared.Models.SnAccount", "Account") + .WithMany() + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_wallets_accounts_account_id"); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnWalletFund", b => + { + b.HasOne("DysonNetwork.Shared.Models.SnAccount", "CreatorAccount") + .WithMany() + .HasForeignKey("CreatorAccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_wallet_funds_accounts_creator_account_id"); + + b.Navigation("CreatorAccount"); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnWalletFundRecipient", b => + { + b.HasOne("DysonNetwork.Shared.Models.SnWalletFund", "Fund") + .WithMany("Recipients") + .HasForeignKey("FundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_wallet_fund_recipients_wallet_funds_fund_id"); + + b.HasOne("DysonNetwork.Shared.Models.SnAccount", "RecipientAccount") + .WithMany() + .HasForeignKey("RecipientAccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_wallet_fund_recipients_accounts_recipient_account_id"); + + b.Navigation("Fund"); + + b.Navigation("RecipientAccount"); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnWalletGift", b => + { + b.HasOne("DysonNetwork.Shared.Models.SnWalletCoupon", "Coupon") + .WithMany() + .HasForeignKey("CouponId") + .HasConstraintName("fk_wallet_gifts_wallet_coupons_coupon_id"); + + b.HasOne("DysonNetwork.Shared.Models.SnAccount", "Gifter") + .WithMany() + .HasForeignKey("GifterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_wallet_gifts_accounts_gifter_id"); + + b.HasOne("DysonNetwork.Shared.Models.SnAccount", "Recipient") + .WithMany() + .HasForeignKey("RecipientId") + .HasConstraintName("fk_wallet_gifts_accounts_recipient_id"); + + b.HasOne("DysonNetwork.Shared.Models.SnAccount", "Redeemer") + .WithMany() + .HasForeignKey("RedeemerId") + .HasConstraintName("fk_wallet_gifts_accounts_redeemer_id"); + + b.HasOne("DysonNetwork.Shared.Models.SnWalletSubscription", "Subscription") + .WithOne("Gift") + .HasForeignKey("DysonNetwork.Shared.Models.SnWalletGift", "SubscriptionId") + .HasConstraintName("fk_wallet_gifts_wallet_subscriptions_subscription_id"); + + b.Navigation("Coupon"); + + b.Navigation("Gifter"); + + b.Navigation("Recipient"); + + b.Navigation("Redeemer"); + + b.Navigation("Subscription"); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnWalletOrder", b => + { + b.HasOne("DysonNetwork.Shared.Models.SnWallet", "PayeeWallet") + .WithMany() + .HasForeignKey("PayeeWalletId") + .HasConstraintName("fk_payment_orders_wallets_payee_wallet_id"); + + b.HasOne("DysonNetwork.Shared.Models.SnWalletTransaction", "Transaction") + .WithMany() + .HasForeignKey("TransactionId") + .HasConstraintName("fk_payment_orders_payment_transactions_transaction_id"); + + b.Navigation("PayeeWallet"); + + b.Navigation("Transaction"); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnWalletPocket", b => + { + b.HasOne("DysonNetwork.Shared.Models.SnWallet", "Wallet") + .WithMany("Pockets") + .HasForeignKey("WalletId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_wallet_pockets_wallets_wallet_id"); + + b.Navigation("Wallet"); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnWalletSubscription", b => + { + b.HasOne("DysonNetwork.Shared.Models.SnAccount", "Account") + .WithMany() + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_wallet_subscriptions_accounts_account_id"); + + b.HasOne("DysonNetwork.Shared.Models.SnWalletCoupon", "Coupon") + .WithMany() + .HasForeignKey("CouponId") + .HasConstraintName("fk_wallet_subscriptions_wallet_coupons_coupon_id"); + + b.Navigation("Account"); + + b.Navigation("Coupon"); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnWalletTransaction", b => + { + b.HasOne("DysonNetwork.Shared.Models.SnWallet", "PayeeWallet") + .WithMany() + .HasForeignKey("PayeeWalletId") + .HasConstraintName("fk_payment_transactions_wallets_payee_wallet_id"); + + b.HasOne("DysonNetwork.Shared.Models.SnWallet", "PayerWallet") + .WithMany() + .HasForeignKey("PayerWalletId") + .HasConstraintName("fk_payment_transactions_wallets_payer_wallet_id"); + + b.Navigation("PayeeWallet"); + + b.Navigation("PayerWallet"); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnAccount", b => + { + b.Navigation("AuthFactors"); + + b.Navigation("Badges"); + + b.Navigation("Challenges"); + + b.Navigation("Connections"); + + b.Navigation("Contacts"); + + b.Navigation("IncomingRelationships"); + + b.Navigation("OutgoingRelationships"); + + b.Navigation("Profile") + .IsRequired(); + + b.Navigation("Sessions"); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnPermissionGroup", b => + { + b.Navigation("Members"); + + b.Navigation("Nodes"); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnRealm", b => + { + b.Navigation("Members"); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnWallet", b => + { + b.Navigation("Pockets"); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnWalletFund", b => + { + b.Navigation("Recipients"); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnWalletSubscription", b => + { + b.Navigation("Gift"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/DysonNetwork.Pass/Migrations/20251202160759_SimplifiedAuthSession.cs b/DysonNetwork.Pass/Migrations/20251202160759_SimplifiedAuthSession.cs new file mode 100644 index 0000000..dc75a81 --- /dev/null +++ b/DysonNetwork.Pass/Migrations/20251202160759_SimplifiedAuthSession.cs @@ -0,0 +1,105 @@ +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace DysonNetwork.Pass.Migrations +{ + /// + public partial class SimplifiedAuthSession : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "fk_auth_sessions_auth_challenges_challenge_id", + table: "auth_sessions"); + + migrationBuilder.DropIndex( + name: "ix_auth_sessions_challenge_id", + table: "auth_sessions"); + + migrationBuilder.DropColumn( + name: "type", + table: "auth_challenges"); + + migrationBuilder.AddColumn>( + name: "audiences", + table: "auth_sessions", + type: "jsonb", + nullable: false, + defaultValue: new List()); + + migrationBuilder.AddColumn( + name: "ip_address", + table: "auth_sessions", + type: "character varying(128)", + maxLength: 128, + nullable: true); + + migrationBuilder.AddColumn>( + name: "scopes", + table: "auth_sessions", + type: "jsonb", + nullable: false, + defaultValue: new List()); + + migrationBuilder.AddColumn( + name: "type", + table: "auth_sessions", + type: "integer", + nullable: false, + defaultValue: 0); + + migrationBuilder.AddColumn( + name: "user_agent", + table: "auth_sessions", + type: "character varying(512)", + maxLength: 512, + nullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "audiences", + table: "auth_sessions"); + + migrationBuilder.DropColumn( + name: "ip_address", + table: "auth_sessions"); + + migrationBuilder.DropColumn( + name: "scopes", + table: "auth_sessions"); + + migrationBuilder.DropColumn( + name: "type", + table: "auth_sessions"); + + migrationBuilder.DropColumn( + name: "user_agent", + table: "auth_sessions"); + + migrationBuilder.AddColumn( + name: "type", + table: "auth_challenges", + type: "integer", + nullable: false, + defaultValue: 0); + + migrationBuilder.CreateIndex( + name: "ix_auth_sessions_challenge_id", + table: "auth_sessions", + column: "challenge_id"); + + migrationBuilder.AddForeignKey( + name: "fk_auth_sessions_auth_challenges_challenge_id", + table: "auth_sessions", + column: "challenge_id", + principalTable: "auth_challenges", + principalColumn: "id"); + } + } +} diff --git a/DysonNetwork.Pass/Migrations/AppDatabaseModelSnapshot.cs b/DysonNetwork.Pass/Migrations/AppDatabaseModelSnapshot.cs index 2d00858..7f14700 100644 --- a/DysonNetwork.Pass/Migrations/AppDatabaseModelSnapshot.cs +++ b/DysonNetwork.Pass/Migrations/AppDatabaseModelSnapshot.cs @@ -933,10 +933,6 @@ namespace DysonNetwork.Pass.Migrations .HasColumnType("integer") .HasColumnName("step_total"); - b.Property("Type") - .HasColumnType("integer") - .HasColumnName("type"); - b.Property("UpdatedAt") .HasColumnType("timestamp with time zone") .HasColumnName("updated_at"); @@ -1023,6 +1019,11 @@ namespace DysonNetwork.Pass.Migrations .HasColumnType("uuid") .HasColumnName("app_id"); + b.Property>("Audiences") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("audiences"); + b.Property("ChallengeId") .HasColumnType("uuid") .HasColumnName("challenge_id"); @@ -1043,6 +1044,11 @@ namespace DysonNetwork.Pass.Migrations .HasColumnType("timestamp with time zone") .HasColumnName("expired_at"); + b.Property("IpAddress") + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("ip_address"); + b.Property("LastGrantedAt") .HasColumnType("timestamp with time zone") .HasColumnName("last_granted_at"); @@ -1051,19 +1057,30 @@ namespace DysonNetwork.Pass.Migrations .HasColumnType("uuid") .HasColumnName("parent_session_id"); + b.Property>("Scopes") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("scopes"); + + b.Property("Type") + .HasColumnType("integer") + .HasColumnName("type"); + b.Property("UpdatedAt") .HasColumnType("timestamp with time zone") .HasColumnName("updated_at"); + b.Property("UserAgent") + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasColumnName("user_agent"); + b.HasKey("Id") .HasName("pk_auth_sessions"); b.HasIndex("AccountId") .HasDatabaseName("ix_auth_sessions_account_id"); - b.HasIndex("ChallengeId") - .HasDatabaseName("ix_auth_sessions_challenge_id"); - b.HasIndex("ClientId") .HasDatabaseName("ix_auth_sessions_client_id"); @@ -2537,11 +2554,6 @@ namespace DysonNetwork.Pass.Migrations .IsRequired() .HasConstraintName("fk_auth_sessions_accounts_account_id"); - b.HasOne("DysonNetwork.Shared.Models.SnAuthChallenge", "Challenge") - .WithMany() - .HasForeignKey("ChallengeId") - .HasConstraintName("fk_auth_sessions_auth_challenges_challenge_id"); - b.HasOne("DysonNetwork.Shared.Models.SnAuthClient", "Client") .WithMany() .HasForeignKey("ClientId") @@ -2554,8 +2566,6 @@ namespace DysonNetwork.Pass.Migrations b.Navigation("Account"); - b.Navigation("Challenge"); - b.Navigation("Client"); b.Navigation("ParentSession"); diff --git a/DysonNetwork.Ring/Connection/WebSocketController.cs b/DysonNetwork.Ring/Connection/WebSocketController.cs index 09b2777..d8677c8 100644 --- a/DysonNetwork.Ring/Connection/WebSocketController.cs +++ b/DysonNetwork.Ring/Connection/WebSocketController.cs @@ -4,7 +4,6 @@ using DysonNetwork.Shared.Stream; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using NATS.Client.Core; -using NATS.Net; using Swashbuckle.AspNetCore.Annotations; using WebSocketPacket = DysonNetwork.Shared.Models.WebSocketPacket; @@ -40,10 +39,10 @@ public class WebSocketController( } var accountId = Guid.Parse(currentUser.Id!); - var deviceId = currentSession.Challenge?.DeviceId ?? Guid.NewGuid().ToString(); + var deviceId = currentSession.ClientId; - // TODO temporary fix due to the server update - if (string.IsNullOrEmpty(deviceId)) deviceId = Guid.NewGuid().ToString().Replace("-", ""); + if (string.IsNullOrEmpty(deviceId)) + return BadRequest("Unable to determine device id"); if (deviceAlt is not null) deviceId = $"{deviceId}+{deviceAlt}"; diff --git a/DysonNetwork.Ring/Notification/NotificationController.cs b/DysonNetwork.Ring/Notification/NotificationController.cs index 1624539..8e72ddf 100644 --- a/DysonNetwork.Ring/Notification/NotificationController.cs +++ b/DysonNetwork.Ring/Notification/NotificationController.cs @@ -93,7 +93,7 @@ public class NotificationController( var result = await nty.SubscribeDevice( - currentSession.Challenge.DeviceId, + currentSession.ClientId, request.DeviceToken, request.Provider, currentUser @@ -117,7 +117,7 @@ public class NotificationController( var affectedRows = await db.PushSubscriptions .Where(s => s.AccountId == accountId && - s.DeviceId == currentSession.Challenge.DeviceId + s.DeviceId == currentSession.ClientId ).ExecuteDeleteAsync(); return Ok(affectedRows); } diff --git a/DysonNetwork.Shared/Auth/AuthScheme.cs b/DysonNetwork.Shared/Auth/AuthScheme.cs index 5fc0a6f..1660aa0 100644 --- a/DysonNetwork.Shared/Auth/AuthScheme.cs +++ b/DysonNetwork.Shared/Auth/AuthScheme.cs @@ -60,7 +60,7 @@ public class DysonTokenAuthHandler( }; // Add scopes as claims - session.Challenge?.Scopes.ToList().ForEach(scope => claims.Add(new Claim("scope", scope))); + session.Scopes.ToList().ForEach(scope => claims.Add(new Claim("scope", scope))); // Add superuser claim if applicable if (session.Account.IsSuperuser) diff --git a/DysonNetwork.Shared/DysonNetwork.Shared.csproj b/DysonNetwork.Shared/DysonNetwork.Shared.csproj index ebf2020..a9f23fa 100644 --- a/DysonNetwork.Shared/DysonNetwork.Shared.csproj +++ b/DysonNetwork.Shared/DysonNetwork.Shared.csproj @@ -22,7 +22,8 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + + diff --git a/DysonNetwork.Shared/Extensions.cs b/DysonNetwork.Shared/Extensions.cs index d4252ab..90f0348 100644 --- a/DysonNetwork.Shared/Extensions.cs +++ b/DysonNetwork.Shared/Extensions.cs @@ -70,7 +70,7 @@ public static class Extensions return RedLockFactory.Create(new List { new(mux) }); }); builder.Services.AddSingleton(); - builder.Services.AddSingleton(); + builder.Services.AddSingleton(); return builder; } diff --git a/DysonNetwork.Shared/Models/AuthSession.cs b/DysonNetwork.Shared/Models/AuthSession.cs index 8b4cf22..f5d204e 100644 --- a/DysonNetwork.Shared/Models/AuthSession.cs +++ b/DysonNetwork.Shared/Models/AuthSession.cs @@ -2,24 +2,34 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using System.Text.Json.Serialization; using DysonNetwork.Shared.GeoIp; +using DysonNetwork.Shared.Proto; using NodaTime; using NodaTime.Serialization.Protobuf; namespace DysonNetwork.Shared.Models; +public enum SessionType +{ + Login, + OAuth, // Trying to authorize other platforms + Oidc // Trying to connect other platforms +} + public class SnAuthSession : ModelBase { public Guid Id { get; set; } = Guid.NewGuid(); + public SessionType Type { get; set; } = SessionType.Login; public Instant? LastGrantedAt { get; set; } public Instant? ExpiredAt { get; set; } + + [Column(TypeName = "jsonb")] public List Audiences { get; set; } = []; + [Column(TypeName = "jsonb")] public List Scopes { get; set; } = []; + [MaxLength(128)] public string? IpAddress { get; set; } + [MaxLength(512)] public string? UserAgent { get; set; } public Guid AccountId { get; set; } [JsonIgnore] public SnAccount Account { get; set; } = null!; - // The challenge that created this session - public Guid? ChallengeId { get; set; } - public SnAuthChallenge? Challenge { get; set; } = null!; - // The client device for this session public Guid? ClientId { get; set; } public SnAuthClient? Client { get; set; } = null!; @@ -28,30 +38,41 @@ public class SnAuthSession : ModelBase public Guid? ParentSessionId { get; set; } public SnAuthSession? ParentSession { get; set; } + // The origin challenge for this session + public Guid? ChallengeId { get; set; } + // Indicates the session is for an OIDC connection public Guid? AppId { get; set; } - public Proto.AuthSession ToProtoValue() => new() + public AuthSession ToProtoValue() { - Id = Id.ToString(), - LastGrantedAt = LastGrantedAt?.ToTimestamp(), - ExpiredAt = ExpiredAt?.ToTimestamp(), - AccountId = AccountId.ToString(), - Account = Account.ToProtoValue(), - ChallengeId = ChallengeId.ToString(), - Challenge = Challenge?.ToProtoValue(), - ClientId = ClientId.ToString(), - Client = Client?.ToProtoValue(), - ParentSessionId = ParentSessionId.ToString(), - AppId = AppId?.ToString() - }; -} + var proto = new AuthSession + { + Id = Id.ToString(), + LastGrantedAt = LastGrantedAt?.ToTimestamp(), + Type = Type switch + { + SessionType.Login => Proto.SessionType.Login, + SessionType.OAuth => Proto.SessionType.Oauth, + SessionType.Oidc => Proto.SessionType.Oidc, + _ => Proto.SessionType.ChallengeTypeUnspecified + }, + IpAddress = IpAddress, + UserAgent = UserAgent, + ExpiredAt = ExpiredAt?.ToTimestamp(), + AccountId = AccountId.ToString(), + Account = Account.ToProtoValue(), + ClientId = ClientId.ToString(), + Client = Client?.ToProtoValue(), + ParentSessionId = ParentSessionId.ToString(), + AppId = AppId?.ToString() + }; + + proto.Audiences.AddRange(Audiences); + proto.Scopes.AddRange(Scopes); -public enum ChallengeType -{ - Login, - OAuth, // Trying to authorize other platforms - Oidc // Trying to connect other platforms + return proto; + } } public enum ClientPlatform @@ -72,10 +93,9 @@ public class SnAuthChallenge : ModelBase 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 BlacklistFactors { get; set; } = new(); - [Column(TypeName = "jsonb")] public List Audiences { get; set; } = new(); - [Column(TypeName = "jsonb")] public List Scopes { get; set; } = new(); + [Column(TypeName = "jsonb")] public List BlacklistFactors { get; set; } = []; + [Column(TypeName = "jsonb")] public List Audiences { get; set; } = []; + [Column(TypeName = "jsonb")] public List Scopes { get; set; } = []; [MaxLength(128)] public string? IpAddress { get; set; } [MaxLength(512)] public string? UserAgent { get; set; } [MaxLength(512)] public string DeviceId { get; set; } = null!; @@ -93,14 +113,13 @@ public class SnAuthChallenge : ModelBase return this; } - public Proto.AuthChallenge ToProtoValue() => new() + public AuthChallenge ToProtoValue() => new() { Id = Id.ToString(), ExpiredAt = ExpiredAt?.ToTimestamp(), StepRemain = StepRemain, StepTotal = StepTotal, FailedAttempts = FailedAttempts, - Type = (Proto.ChallengeType)Type, BlacklistFactors = { BlacklistFactors.Select(x => x.ToString()) }, Audiences = { Audiences }, Scopes = { Scopes }, @@ -150,4 +169,4 @@ public class SnAuthClientWithChallenge : SnAuthClient AccountId = client.AccountId, }; } -} \ No newline at end of file +} diff --git a/DysonNetwork.Shared/Proto/auth.proto b/DysonNetwork.Shared/Proto/auth.proto index 7e4d39e..419d0d6 100644 --- a/DysonNetwork.Shared/Proto/auth.proto +++ b/DysonNetwork.Shared/Proto/auth.proto @@ -17,12 +17,15 @@ message AuthSession { optional google.protobuf.Timestamp expired_at = 4; string account_id = 5; Account account = 6; - string challenge_id = 7; - AuthChallenge challenge = 8; google.protobuf.StringValue app_id = 9; optional string client_id = 10; optional string parent_session_id = 11; AuthClient client = 12; + repeated string audiences = 13; + repeated string scopes = 14; + google.protobuf.StringValue ip_address = 15; + google.protobuf.StringValue user_agent = 16; + SessionType type = 17; } // Represents an authentication challenge @@ -32,7 +35,6 @@ message AuthChallenge { int32 step_remain = 3; int32 step_total = 4; int32 failed_attempts = 5; - ChallengeType type = 7; repeated string blacklist_factors = 8; repeated string audiences = 9; repeated string scopes = 10; @@ -56,7 +58,7 @@ message AuthClient { } // Enum for challenge types -enum ChallengeType { +enum SessionType { CHALLENGE_TYPE_UNSPECIFIED = 0; LOGIN = 1; OAUTH = 2; diff --git a/DysonNetwork.sln.DotSettings.user b/DysonNetwork.sln.DotSettings.user index 68285df..0dabc6e 100644 --- a/DysonNetwork.sln.DotSettings.user +++ b/DysonNetwork.sln.DotSettings.user @@ -25,6 +25,7 @@ ForceIncluded ForceIncluded ForceIncluded + ForceIncluded ForceIncluded ForceIncluded ForceIncluded @@ -107,6 +108,7 @@ ForceIncluded ForceIncluded ForceIncluded + ForceIncluded ForceIncluded ForceIncluded ForceIncluded