From e76c80eead0552dba455f9480394353e0bf84986 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Fri, 11 Jul 2025 02:00:40 +0800 Subject: [PATCH 01/42] :recycle: Moved some services to DysonNetwork.Pass --- DysonNetwork.Pass/Account/AbuseReport.cs | 30 + DysonNetwork.Pass/Account/Account.cs | 190 +++++ .../Account/AccountController.cs | 178 +++++ .../Account/AccountCurrentController.cs | 703 ++++++++++++++++++ .../Account/AccountEventService.cs | 339 +++++++++ DysonNetwork.Pass/Account/AccountService.cs | 657 ++++++++++++++++ .../Account/AccountUsernameService.cs | 105 +++ DysonNetwork.Pass/Account/ActionLog.cs | 58 ++ DysonNetwork.Pass/Account/ActionLogService.cs | 46 ++ DysonNetwork.Pass/Account/Badge.cs | 47 ++ DysonNetwork.Pass/Account/Event.cs | 65 ++ DysonNetwork.Pass/Account/MagicSpell.cs | 30 + .../Account/MagicSpellController.cs | 19 + .../Account/MagicSpellService.cs | 252 +++++++ DysonNetwork.Pass/Account/Notification.cs | 41 + .../Account/NotificationController.cs | 166 +++++ .../Account/NotificationService.cs | 308 ++++++++ DysonNetwork.Pass/Account/Relationship.cs | 22 + .../Account/RelationshipController.cs | 253 +++++++ .../Account/RelationshipService.cs | 207 ++++++ DysonNetwork.Pass/Account/VerificationMark.cs | 25 + DysonNetwork.Pass/AppDatabase.cs | 275 +++++++ DysonNetwork.Pass/Auth/Auth.cs | 273 +++++++ DysonNetwork.Pass/Auth/AuthController.cs | 266 +++++++ DysonNetwork.Pass/Auth/AuthService.cs | 304 ++++++++ DysonNetwork.Pass/Auth/CheckpointModel.cs | 6 + DysonNetwork.Pass/Auth/CompactTokenService.cs | 94 +++ .../Controllers/OidcProviderController.cs | 241 ++++++ .../Models/AuthorizationCodeInfo.cs | 17 + .../Options/OidcProviderOptions.cs | 36 + .../Responses/AuthorizationResponse.cs | 23 + .../OidcProvider/Responses/ErrorResponse.cs | 20 + .../OidcProvider/Responses/TokenResponse.cs | 26 + .../Services/OidcProviderService.cs | 394 ++++++++++ .../Auth/OpenId/AfdianOidcService.cs | 95 +++ .../Auth/OpenId/AppleMobileSignInRequest.cs | 19 + .../Auth/OpenId/AppleOidcService.cs | 280 +++++++ .../Auth/OpenId/ConnectionController.cs | 409 ++++++++++ .../Auth/OpenId/DiscordOidcService.cs | 116 +++ .../Auth/OpenId/GitHubOidcService.cs | 128 ++++ .../Auth/OpenId/GoogleOidcService.cs | 137 ++++ .../Auth/OpenId/MicrosoftOidcService.cs | 123 +++ .../Auth/OpenId/OidcController.cs | 194 +++++ DysonNetwork.Pass/Auth/OpenId/OidcService.cs | 294 ++++++++ DysonNetwork.Pass/Auth/OpenId/OidcState.cs | 189 +++++ DysonNetwork.Pass/Auth/OpenId/OidcUserInfo.cs | 49 ++ DysonNetwork.Pass/Auth/Session.cs | 69 ++ DysonNetwork.Pass/Dockerfile | 23 + DysonNetwork.Pass/DysonNetwork.Pass.csproj | 42 ++ DysonNetwork.Pass/DysonNetwork.Pass.http | 6 + .../Handlers/ActionLogFlushHandler.cs | 25 + .../Handlers/LastActiveFlushHandler.cs | 61 ++ DysonNetwork.Pass/Permission/Permission.cs | 59 ++ .../Permission/PermissionMiddleware.cs | 51 ++ .../Permission/PermissionService.cs | 198 +++++ DysonNetwork.Pass/Program.cs | 23 + .../Properties/launchSettings.json | 23 + DysonNetwork.Pass/WeatherForecast.cs | 12 + .../appsettings.Development.json | 8 + DysonNetwork.Pass/appsettings.json | 9 + DysonNetwork.Shared/Cache/CacheService.cs | 396 ++++++++++ .../Cache/FlushBufferService.cs | 66 ++ .../DysonNetwork.Shared.csproj | 18 + DysonNetwork.Shared/Geo/GeoIpService.cs | 56 ++ .../DysonNetwork.Sphere.csproj | 4 +- DysonNetwork.Sphere/Storage/FileController.cs | 8 +- DysonNetwork.sln | 12 + DysonNetwork.sln.DotSettings.user | 2 +- 68 files changed, 8913 insertions(+), 7 deletions(-) create mode 100644 DysonNetwork.Pass/Account/AbuseReport.cs create mode 100644 DysonNetwork.Pass/Account/Account.cs create mode 100644 DysonNetwork.Pass/Account/AccountController.cs create mode 100644 DysonNetwork.Pass/Account/AccountCurrentController.cs create mode 100644 DysonNetwork.Pass/Account/AccountEventService.cs create mode 100644 DysonNetwork.Pass/Account/AccountService.cs create mode 100644 DysonNetwork.Pass/Account/AccountUsernameService.cs create mode 100644 DysonNetwork.Pass/Account/ActionLog.cs create mode 100644 DysonNetwork.Pass/Account/ActionLogService.cs create mode 100644 DysonNetwork.Pass/Account/Badge.cs create mode 100644 DysonNetwork.Pass/Account/Event.cs create mode 100644 DysonNetwork.Pass/Account/MagicSpell.cs create mode 100644 DysonNetwork.Pass/Account/MagicSpellController.cs create mode 100644 DysonNetwork.Pass/Account/MagicSpellService.cs create mode 100644 DysonNetwork.Pass/Account/Notification.cs create mode 100644 DysonNetwork.Pass/Account/NotificationController.cs create mode 100644 DysonNetwork.Pass/Account/NotificationService.cs create mode 100644 DysonNetwork.Pass/Account/Relationship.cs create mode 100644 DysonNetwork.Pass/Account/RelationshipController.cs create mode 100644 DysonNetwork.Pass/Account/RelationshipService.cs create mode 100644 DysonNetwork.Pass/Account/VerificationMark.cs create mode 100644 DysonNetwork.Pass/AppDatabase.cs create mode 100644 DysonNetwork.Pass/Auth/Auth.cs create mode 100644 DysonNetwork.Pass/Auth/AuthController.cs create mode 100644 DysonNetwork.Pass/Auth/AuthService.cs create mode 100644 DysonNetwork.Pass/Auth/CheckpointModel.cs create mode 100644 DysonNetwork.Pass/Auth/CompactTokenService.cs create mode 100644 DysonNetwork.Pass/Auth/OidcProvider/Controllers/OidcProviderController.cs create mode 100644 DysonNetwork.Pass/Auth/OidcProvider/Models/AuthorizationCodeInfo.cs create mode 100644 DysonNetwork.Pass/Auth/OidcProvider/Options/OidcProviderOptions.cs create mode 100644 DysonNetwork.Pass/Auth/OidcProvider/Responses/AuthorizationResponse.cs create mode 100644 DysonNetwork.Pass/Auth/OidcProvider/Responses/ErrorResponse.cs create mode 100644 DysonNetwork.Pass/Auth/OidcProvider/Responses/TokenResponse.cs create mode 100644 DysonNetwork.Pass/Auth/OidcProvider/Services/OidcProviderService.cs create mode 100644 DysonNetwork.Pass/Auth/OpenId/AfdianOidcService.cs create mode 100644 DysonNetwork.Pass/Auth/OpenId/AppleMobileSignInRequest.cs create mode 100644 DysonNetwork.Pass/Auth/OpenId/AppleOidcService.cs create mode 100644 DysonNetwork.Pass/Auth/OpenId/ConnectionController.cs create mode 100644 DysonNetwork.Pass/Auth/OpenId/DiscordOidcService.cs create mode 100644 DysonNetwork.Pass/Auth/OpenId/GitHubOidcService.cs create mode 100644 DysonNetwork.Pass/Auth/OpenId/GoogleOidcService.cs create mode 100644 DysonNetwork.Pass/Auth/OpenId/MicrosoftOidcService.cs create mode 100644 DysonNetwork.Pass/Auth/OpenId/OidcController.cs create mode 100644 DysonNetwork.Pass/Auth/OpenId/OidcService.cs create mode 100644 DysonNetwork.Pass/Auth/OpenId/OidcState.cs create mode 100644 DysonNetwork.Pass/Auth/OpenId/OidcUserInfo.cs create mode 100644 DysonNetwork.Pass/Auth/Session.cs create mode 100644 DysonNetwork.Pass/Dockerfile create mode 100644 DysonNetwork.Pass/DysonNetwork.Pass.csproj create mode 100644 DysonNetwork.Pass/DysonNetwork.Pass.http create mode 100644 DysonNetwork.Pass/Handlers/ActionLogFlushHandler.cs create mode 100644 DysonNetwork.Pass/Handlers/LastActiveFlushHandler.cs create mode 100644 DysonNetwork.Pass/Permission/Permission.cs create mode 100644 DysonNetwork.Pass/Permission/PermissionMiddleware.cs create mode 100644 DysonNetwork.Pass/Permission/PermissionService.cs create mode 100644 DysonNetwork.Pass/Program.cs create mode 100644 DysonNetwork.Pass/Properties/launchSettings.json create mode 100644 DysonNetwork.Pass/WeatherForecast.cs create mode 100644 DysonNetwork.Pass/appsettings.Development.json create mode 100644 DysonNetwork.Pass/appsettings.json create mode 100644 DysonNetwork.Shared/Cache/CacheService.cs create mode 100644 DysonNetwork.Shared/Cache/FlushBufferService.cs create mode 100644 DysonNetwork.Shared/DysonNetwork.Shared.csproj create mode 100644 DysonNetwork.Shared/Geo/GeoIpService.cs diff --git a/DysonNetwork.Pass/Account/AbuseReport.cs b/DysonNetwork.Pass/Account/AbuseReport.cs new file mode 100644 index 0000000..ebb7a26 --- /dev/null +++ b/DysonNetwork.Pass/Account/AbuseReport.cs @@ -0,0 +1,30 @@ +using System.ComponentModel.DataAnnotations; +using NodaTime; + +namespace DysonNetwork.Pass.Account; + +public enum AbuseReportType +{ + Copyright, + Harassment, + Impersonation, + OffensiveContent, + Spam, + PrivacyViolation, + IllegalContent, + Other +} + +public class AbuseReport : ModelBase +{ + public Guid Id { get; set; } = Guid.NewGuid(); + [MaxLength(4096)] public string ResourceIdentifier { get; set; } = null!; + public AbuseReportType Type { get; set; } + [MaxLength(8192)] public string Reason { get; set; } = null!; + + public Instant? ResolvedAt { get; set; } + [MaxLength(8192)] public string? Resolution { get; set; } + + public Guid AccountId { get; set; } + public Account Account { get; set; } = null!; +} \ No newline at end of file diff --git a/DysonNetwork.Pass/Account/Account.cs b/DysonNetwork.Pass/Account/Account.cs new file mode 100644 index 0000000..6674ae2 --- /dev/null +++ b/DysonNetwork.Pass/Account/Account.cs @@ -0,0 +1,190 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Text.Json.Serialization; +using Microsoft.EntityFrameworkCore; +using NodaTime; +using OtpNet; + +namespace DysonNetwork.Pass.Account; + +[Index(nameof(Name), IsUnique = true)] +public class Account : ModelBase +{ + public Guid Id { get; set; } + [MaxLength(256)] public string Name { get; set; } = string.Empty; + [MaxLength(256)] public string Nick { get; set; } = string.Empty; + [MaxLength(32)] public string Language { get; set; } = string.Empty; + public Instant? ActivatedAt { get; set; } + public bool IsSuperuser { get; set; } = false; + + public Profile Profile { get; set; } = null!; + public ICollection Contacts { get; set; } = new List(); + public ICollection Badges { get; set; } = new List(); + + [JsonIgnore] public ICollection AuthFactors { get; set; } = new List(); + [JsonIgnore] public ICollection Connections { get; set; } = new List(); + [JsonIgnore] public ICollection Sessions { get; set; } = new List(); + [JsonIgnore] public ICollection Challenges { get; set; } = new List(); + + [JsonIgnore] public ICollection OutgoingRelationships { get; set; } = new List(); + [JsonIgnore] public ICollection IncomingRelationships { get; set; } = new List(); +} + +public abstract class Leveling +{ + public static readonly List ExperiencePerLevel = + [ + 0, // Level 0 + 100, // Level 1 + 250, // Level 2 + 500, // Level 3 + 1000, // Level 4 + 2000, // Level 5 + 4000, // Level 6 + 8000, // Level 7 + 16000, // Level 8 + 32000, // Level 9 + 64000, // Level 10 + 128000, // Level 11 + 256000, // Level 12 + 512000, // Level 13 + 1024000 // Level 14 + ]; +} + +public class Profile : ModelBase +{ + public Guid Id { get; set; } + [MaxLength(256)] public string? FirstName { get; set; } + [MaxLength(256)] public string? MiddleName { get; set; } + [MaxLength(256)] public string? LastName { get; set; } + [MaxLength(4096)] public string? Bio { get; set; } + [MaxLength(1024)] public string? Gender { get; set; } + [MaxLength(1024)] public string? Pronouns { get; set; } + [MaxLength(1024)] public string? TimeZone { get; set; } + [MaxLength(1024)] public string? Location { get; set; } + public Instant? Birthday { get; set; } + public Instant? LastSeenAt { get; set; } + + [Column(TypeName = "jsonb")] public VerificationMark? Verification { get; set; } + [Column(TypeName = "jsonb")] public BadgeReferenceObject? ActiveBadge { get; set; } + + public int Experience { get; set; } = 0; + [NotMapped] public int Level => Leveling.ExperiencePerLevel.Count(xp => Experience >= xp) - 1; + + [NotMapped] + public double LevelingProgress => Level >= Leveling.ExperiencePerLevel.Count - 1 + ? 100 + : (Experience - Leveling.ExperiencePerLevel[Level]) * 100.0 / + (Leveling.ExperiencePerLevel[Level + 1] - Leveling.ExperiencePerLevel[Level]); + + // Outdated fields, for backward compability + [MaxLength(32)] public string? PictureId { get; set; } + [MaxLength(32)] public string? BackgroundId { get; set; } + + [Column(TypeName = "jsonb")] public CloudFileReferenceObject? Picture { get; set; } + [Column(TypeName = "jsonb")] public CloudFileReferenceObject? Background { get; set; } + + public Guid AccountId { get; set; } + [JsonIgnore] public Account Account { get; set; } = null!; +} + +public class AccountContact : ModelBase +{ + public Guid Id { get; set; } + public AccountContactType Type { get; set; } + public Instant? VerifiedAt { get; set; } + public bool IsPrimary { get; set; } = false; + [MaxLength(1024)] public string Content { get; set; } = string.Empty; + + public Guid AccountId { get; set; } + [JsonIgnore] public Account Account { get; set; } = null!; +} + +public enum AccountContactType +{ + Email, + PhoneNumber, + Address +} + +public class AccountAuthFactor : ModelBase +{ + public Guid Id { get; set; } + public AccountAuthFactorType Type { get; set; } + [JsonIgnore] [MaxLength(8196)] public string? Secret { get; set; } + + [JsonIgnore] + [Column(TypeName = "jsonb")] + public Dictionary? Config { get; set; } = new(); + + /// + /// The trustworthy stands for how safe is this auth factor. + /// Basically, it affects how many steps it can complete in authentication. + /// Besides, users may need to use some high-trustworthy level auth factors when confirming some dangerous operations. + /// + public int Trustworthy { get; set; } = 1; + + public Instant? EnabledAt { get; set; } + public Instant? ExpiredAt { get; set; } + + public Guid AccountId { get; set; } + [JsonIgnore] public Account Account { get; set; } = null!; + + public AccountAuthFactor HashSecret(int cost = 12) + { + if (Secret == null) return this; + Secret = BCrypt.Net.BCrypt.HashPassword(Secret, workFactor: cost); + return this; + } + + public bool VerifyPassword(string password) + { + if (Secret == null) + throw new InvalidOperationException("Auth factor with no secret cannot be verified with password."); + switch (Type) + { + case AccountAuthFactorType.Password: + case AccountAuthFactorType.PinCode: + return BCrypt.Net.BCrypt.Verify(password, Secret); + case AccountAuthFactorType.TimedCode: + var otp = new Totp(Base32Encoding.ToBytes(Secret)); + return otp.VerifyTotp(DateTime.UtcNow, password, out _, new VerificationWindow(previous: 5, future: 5)); + case AccountAuthFactorType.EmailCode: + case AccountAuthFactorType.InAppCode: + default: + throw new InvalidOperationException("Unsupported verification type, use CheckDeliveredCode instead."); + } + } + + /// + /// This dictionary will be returned to the client and should only be set when it just created. + /// Useful for passing the client some data to finishing setup and recovery code. + /// + [NotMapped] + public Dictionary? CreatedResponse { get; set; } +} + +public enum AccountAuthFactorType +{ + Password, + EmailCode, + InAppCode, + TimedCode, + PinCode, +} + +public class AccountConnection : ModelBase +{ + public Guid Id { get; set; } = Guid.NewGuid(); + [MaxLength(4096)] public string Provider { get; set; } = null!; + [MaxLength(8192)] public string ProvidedIdentifier { get; set; } = null!; + [Column(TypeName = "jsonb")] public Dictionary? Meta { get; set; } = new(); + + [JsonIgnore] [MaxLength(4096)] public string? AccessToken { get; set; } + [JsonIgnore] [MaxLength(4096)] public string? RefreshToken { get; set; } + public Instant? LastUsedAt { get; set; } + + public Guid AccountId { get; set; } + public Account Account { get; set; } = null!; +} \ No newline at end of file diff --git a/DysonNetwork.Pass/Account/AccountController.cs b/DysonNetwork.Pass/Account/AccountController.cs new file mode 100644 index 0000000..b6eabf3 --- /dev/null +++ b/DysonNetwork.Pass/Account/AccountController.cs @@ -0,0 +1,178 @@ +using System.ComponentModel.DataAnnotations; +using DysonNetwork.Pass.Auth; +using DysonNetwork.Pass; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using NodaTime; +using NodaTime.Extensions; +using System.Collections.Generic; +using DysonNetwork.Pass.Account; + +namespace DysonNetwork.Pass.Account; + +[ApiController] +[Route("/api/accounts")] +public class AccountController( + AppDatabase db, + AuthService auth, + AccountService accounts, + AccountEventService events +) : ControllerBase +{ + [HttpGet("{name}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task> GetByName(string name) + { + var account = await db.Accounts + .Include(e => e.Badges) + .Include(e => e.Profile) + .Where(a => a.Name == name) + .FirstOrDefaultAsync(); + return account is null ? new NotFoundResult() : account; + } + + [HttpGet("{name}/badges")] + [ProducesResponseType>(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task>> GetBadgesByName(string name) + { + var account = await db.Accounts + .Include(e => e.Badges) + .Where(a => a.Name == name) + .FirstOrDefaultAsync(); + return account is null ? NotFound() : account.Badges.ToList(); + } + + public class AccountCreateRequest + { + [Required] + [MinLength(2)] + [MaxLength(256)] + [RegularExpression(@"^[A-Za-z0-9_-]+$", + ErrorMessage = "Name can only contain letters, numbers, underscores, and hyphens.") + ] + public string Name { get; set; } = string.Empty; + + [Required] [MaxLength(256)] public string Nick { get; set; } = string.Empty; + + [EmailAddress] + [RegularExpression(@"^[^+]+@[^@]+\.[^@]+$", ErrorMessage = "Email address cannot contain '+' symbol.")] + [Required] + [MaxLength(1024)] + public string Email { get; set; } = string.Empty; + + [Required] + [MinLength(4)] + [MaxLength(128)] + public string Password { get; set; } = string.Empty; + + [MaxLength(128)] public string Language { get; set; } = "en-us"; + + [Required] public string CaptchaToken { get; set; } = string.Empty; + } + + [HttpPost] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + public async Task> CreateAccount([FromBody] AccountCreateRequest request) + { + if (!await auth.ValidateCaptcha(request.CaptchaToken)) return BadRequest("Invalid captcha token."); + + try + { + var account = await accounts.CreateAccount( + request.Name, + request.Nick, + request.Email, + request.Password, + request.Language + ); + return Ok(account); + } + catch (Exception ex) + { + return BadRequest(ex.Message); + } + } + + public class RecoveryPasswordRequest + { + [Required] public string Account { get; set; } = null!; + [Required] public string CaptchaToken { get; set; } = null!; + } + + [HttpPost("recovery/password")] + public async Task RequestResetPassword([FromBody] RecoveryPasswordRequest request) + { + if (!await auth.ValidateCaptcha(request.CaptchaToken)) return BadRequest("Invalid captcha token."); + + var account = await accounts.LookupAccount(request.Account); + if (account is null) return BadRequest("Unable to find the account."); + + try + { + await accounts.RequestPasswordReset(account); + } + catch (InvalidOperationException) + { + return BadRequest("You already requested password reset within 24 hours."); + } + + return Ok(); + } + + public class StatusRequest + { + public StatusAttitude Attitude { get; set; } + public bool IsInvisible { get; set; } + public bool IsNotDisturb { get; set; } + [MaxLength(1024)] public string? Label { get; set; } + public Instant? ClearedAt { get; set; } + } + + [HttpGet("{name}/statuses")] + public async Task> GetOtherStatus(string name) + { + var account = await db.Accounts.FirstOrDefaultAsync(a => a.Name == name); + if (account is null) return BadRequest(); + var status = await events.GetStatus(account.Id); + status.IsInvisible = false; // Keep the invisible field not available for other users + return Ok(status); + } + + [HttpGet("{name}/calendar")] + public async Task>> GetOtherEventCalendar( + string name, + [FromQuery] int? month, + [FromQuery] int? year + ) + { + var currentDate = SystemClock.Instance.GetCurrentInstant().InUtc().Date; + month ??= currentDate.Month; + year ??= currentDate.Year; + + if (month is < 1 or > 12) return BadRequest("Invalid month."); + if (year < 1) return BadRequest("Invalid year."); + + var account = await db.Accounts.FirstOrDefaultAsync(a => a.Name == name); + if (account is null) return BadRequest(); + + var calendar = await events.GetEventCalendar(account, month.Value, year.Value, replaceInvisible: true); + return Ok(calendar); + } + + [HttpGet("search")] + public async Task> Search([FromQuery] string query, [FromQuery] int take = 20) + { + if (string.IsNullOrWhiteSpace(query)) + return []; + return await db.Accounts + .Include(e => e.Profile) + .Where(a => EF.Functions.ILike(a.Name, $"%{query}%") || + EF.Functions.ILike(a.Nick, $"%{query}%")) + .Take(take) + .ToListAsync(); + } +} \ No newline at end of file diff --git a/DysonNetwork.Pass/Account/AccountCurrentController.cs b/DysonNetwork.Pass/Account/AccountCurrentController.cs new file mode 100644 index 0000000..59642d2 --- /dev/null +++ b/DysonNetwork.Pass/Account/AccountCurrentController.cs @@ -0,0 +1,703 @@ +using System.ComponentModel.DataAnnotations; +using DysonNetwork.Pass.Auth; +using DysonNetwork.Pass; +using DysonNetwork.Pass.Storage; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using NodaTime; +using Org.BouncyCastle.Utilities; + +namespace DysonNetwork.Pass.Account; + +[Authorize] +[ApiController] +[Route("/api/accounts/me")] +public class AccountCurrentController( + AppDatabase db, + AccountService accounts, + FileReferenceService fileRefService, + AccountEventService events, + AuthService auth +) : ControllerBase +{ + [HttpGet] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task> GetCurrentIdentity() + { + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); + var userId = currentUser.Id; + + var account = await db.Accounts + .Include(e => e.Badges) + .Include(e => e.Profile) + .Where(e => e.Id == userId) + .FirstOrDefaultAsync(); + + return Ok(account); + } + + public class BasicInfoRequest + { + [MaxLength(256)] public string? Nick { get; set; } + [MaxLength(32)] public string? Language { get; set; } + } + + [HttpPatch] + public async Task> UpdateBasicInfo([FromBody] BasicInfoRequest request) + { + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); + + var account = await db.Accounts.FirstAsync(a => a.Id == currentUser.Id); + + if (request.Nick is not null) account.Nick = request.Nick; + if (request.Language is not null) account.Language = request.Language; + + await db.SaveChangesAsync(); + await accounts.PurgeAccountCache(currentUser); + return currentUser; + } + + public class ProfileRequest + { + [MaxLength(256)] public string? FirstName { get; set; } + [MaxLength(256)] public string? MiddleName { get; set; } + [MaxLength(256)] public string? LastName { get; set; } + [MaxLength(1024)] public string? Gender { get; set; } + [MaxLength(1024)] public string? Pronouns { get; set; } + [MaxLength(1024)] public string? TimeZone { get; set; } + [MaxLength(1024)] public string? Location { get; set; } + [MaxLength(4096)] public string? Bio { get; set; } + public Instant? Birthday { get; set; } + + [MaxLength(32)] public string? PictureId { get; set; } + [MaxLength(32)] public string? BackgroundId { get; set; } + } + + [HttpPatch("profile")] + public async Task> UpdateProfile([FromBody] ProfileRequest request) + { + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); + var userId = currentUser.Id; + + var profile = await db.AccountProfiles + .Where(p => p.Account.Id == userId) + .FirstOrDefaultAsync(); + if (profile is null) return BadRequest("Unable to get your account."); + + if (request.FirstName is not null) profile.FirstName = request.FirstName; + if (request.MiddleName is not null) profile.MiddleName = request.MiddleName; + if (request.LastName is not null) profile.LastName = request.LastName; + if (request.Bio is not null) profile.Bio = request.Bio; + if (request.Gender is not null) profile.Gender = request.Gender; + if (request.Pronouns is not null) profile.Pronouns = request.Pronouns; + if (request.Birthday is not null) profile.Birthday = request.Birthday; + if (request.Location is not null) profile.Location = request.Location; + if (request.TimeZone is not null) profile.TimeZone = request.TimeZone; + + if (request.PictureId is not null) + { + var picture = await db.Files.Where(f => f.Id == request.PictureId).FirstOrDefaultAsync(); + if (picture is null) return BadRequest("Invalid picture id, unable to find the file on cloud."); + + var profileResourceId = $"profile:{profile.Id}"; + + // Remove old references for the profile picture + if (profile.Picture is not null) + { + var oldPictureRefs = + await fileRefService.GetResourceReferencesAsync(profileResourceId, "profile.picture"); + foreach (var oldRef in oldPictureRefs) + { + await fileRefService.DeleteReferenceAsync(oldRef.Id); + } + } + + profile.Picture = picture.ToReferenceObject(); + + // Create new reference + await fileRefService.CreateReferenceAsync( + picture.Id, + "profile.picture", + profileResourceId + ); + } + + if (request.BackgroundId is not null) + { + var background = await db.Files.Where(f => f.Id == request.BackgroundId).FirstOrDefaultAsync(); + if (background is null) return BadRequest("Invalid background id, unable to find the file on cloud."); + + var profileResourceId = $"profile:{profile.Id}"; + + // Remove old references for the profile background + if (profile.Background is not null) + { + var oldBackgroundRefs = + await fileRefService.GetResourceReferencesAsync(profileResourceId, "profile.background"); + foreach (var oldRef in oldBackgroundRefs) + { + await fileRefService.DeleteReferenceAsync(oldRef.Id); + } + } + + profile.Background = background.ToReferenceObject(); + + // Create new reference + await fileRefService.CreateReferenceAsync( + background.Id, + "profile.background", + profileResourceId + ); + } + + db.Update(profile); + await db.SaveChangesAsync(); + + await accounts.PurgeAccountCache(currentUser); + + return profile; + } + + [HttpDelete] + public async Task RequestDeleteAccount() + { + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); + + try + { + await accounts.RequestAccountDeletion(currentUser); + } + catch (InvalidOperationException) + { + return BadRequest("You already requested account deletion within 24 hours."); + } + + return Ok(); + } + + [HttpGet("statuses")] + public async Task> GetCurrentStatus() + { + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); + var status = await events.GetStatus(currentUser.Id); + return Ok(status); + } + + [HttpPatch("statuses")] + [RequiredPermission("global", "accounts.statuses.update")] + public async Task> UpdateStatus([FromBody] AccountController.StatusRequest request) + { + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); + + var now = SystemClock.Instance.GetCurrentInstant(); + var status = await db.AccountStatuses + .Where(e => e.AccountId == currentUser.Id) + .Where(e => e.ClearedAt == null || e.ClearedAt > now) + .OrderByDescending(e => e.CreatedAt) + .FirstOrDefaultAsync(); + if (status is null) return NotFound(); + + status.Attitude = request.Attitude; + status.IsInvisible = request.IsInvisible; + status.IsNotDisturb = request.IsNotDisturb; + status.Label = request.Label; + status.ClearedAt = request.ClearedAt; + + db.Update(status); + await db.SaveChangesAsync(); + events.PurgeStatusCache(currentUser.Id); + + return status; + } + + [HttpPost("statuses")] + [RequiredPermission("global", "accounts.statuses.create")] + public async Task> CreateStatus([FromBody] AccountController.StatusRequest request) + { + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); + + var status = new Status + { + AccountId = currentUser.Id, + Attitude = request.Attitude, + IsInvisible = request.IsInvisible, + IsNotDisturb = request.IsNotDisturb, + Label = request.Label, + ClearedAt = request.ClearedAt + }; + + return await events.CreateStatus(currentUser, status); + } + + [HttpDelete("me/statuses")] + public async Task DeleteStatus() + { + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); + + var now = SystemClock.Instance.GetCurrentInstant(); + var status = await db.AccountStatuses + .Where(s => s.AccountId == currentUser.Id) + .Where(s => s.ClearedAt == null || s.ClearedAt > now) + .OrderByDescending(s => s.CreatedAt) + .FirstOrDefaultAsync(); + if (status is null) return NotFound(); + + await events.ClearStatus(currentUser, status); + return NoContent(); + } + + [HttpGet("check-in")] + public async Task> GetCheckInResult() + { + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); + var userId = currentUser.Id; + + var now = SystemClock.Instance.GetCurrentInstant(); + var today = now.InUtc().Date; + var startOfDay = today.AtStartOfDayInZone(DateTimeZone.Utc).ToInstant(); + var endOfDay = today.PlusDays(1).AtStartOfDayInZone(DateTimeZone.Utc).ToInstant(); + + var result = await db.AccountCheckInResults + .Where(x => x.AccountId == userId) + .Where(x => x.CreatedAt >= startOfDay && x.CreatedAt < endOfDay) + .OrderByDescending(x => x.CreatedAt) + .FirstOrDefaultAsync(); + + return result is null ? NotFound() : Ok(result); + } + + [HttpPost("check-in")] + public async Task> DoCheckIn([FromBody] string? captchaToken) + { + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); + + var isAvailable = await events.CheckInDailyIsAvailable(currentUser); + if (!isAvailable) + return BadRequest("Check-in is not available for today."); + + try + { + var needsCaptcha = await events.CheckInDailyDoAskCaptcha(currentUser); + return needsCaptcha switch + { + true when string.IsNullOrWhiteSpace(captchaToken) => StatusCode(423, + "Captcha is required for this check-in."), + true when !await auth.ValidateCaptcha(captchaToken!) => BadRequest("Invalid captcha token."), + _ => await events.CheckInDaily(currentUser) + }; + } + catch (InvalidOperationException ex) + { + return BadRequest(ex.Message); + } + } + + [HttpGet("calendar")] + public async Task>> GetEventCalendar([FromQuery] int? month, + [FromQuery] int? year) + { + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); + + var currentDate = SystemClock.Instance.GetCurrentInstant().InUtc().Date; + month ??= currentDate.Month; + year ??= currentDate.Year; + + if (month is < 1 or > 12) return BadRequest("Invalid month."); + if (year < 1) return BadRequest("Invalid year."); + + var calendar = await events.GetEventCalendar(currentUser, month.Value, year.Value); + return Ok(calendar); + } + + [HttpGet("actions")] + [ProducesResponseType>(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + public async Task>> GetActionLogs( + [FromQuery] int take = 20, + [FromQuery] int offset = 0 + ) + { + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); + + var query = db.ActionLogs + .Where(log => log.AccountId == currentUser.Id) + .OrderByDescending(log => log.CreatedAt); + + var total = await query.CountAsync(); + Response.Headers.Append("X-Total", total.ToString()); + + var logs = await query + .Skip(offset) + .Take(take) + .ToListAsync(); + + return Ok(logs); + } + + [HttpGet("factors")] + public async Task>> GetAuthFactors() + { + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); + + var factors = await db.AccountAuthFactors + .Include(f => f.Account) + .Where(f => f.Account.Id == currentUser.Id) + .ToListAsync(); + + return Ok(factors); + } + + public class AuthFactorRequest + { + public AccountAuthFactorType Type { get; set; } + public string? Secret { get; set; } + } + + [HttpPost("factors")] + [Authorize] + public async Task> CreateAuthFactor([FromBody] AuthFactorRequest request) + { + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); + if (await accounts.CheckAuthFactorExists(currentUser, request.Type)) + return BadRequest($"Auth factor with type {request.Type} is already exists."); + + var factor = await accounts.CreateAuthFactor(currentUser, request.Type, request.Secret); + return Ok(factor); + } + + [HttpPost("factors/{id:guid}/enable")] + [Authorize] + public async Task> EnableAuthFactor(Guid id, [FromBody] string? code) + { + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); + + var factor = await db.AccountAuthFactors + .Where(f => f.AccountId == currentUser.Id && f.Id == id) + .FirstOrDefaultAsync(); + if (factor is null) return NotFound(); + + try + { + factor = await accounts.EnableAuthFactor(factor, code); + return Ok(factor); + } + catch (Exception ex) + { + return BadRequest(ex.Message); + } + } + + [HttpPost("factors/{id:guid}/disable")] + [Authorize] + public async Task> DisableAuthFactor(Guid id) + { + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); + + var factor = await db.AccountAuthFactors + .Where(f => f.AccountId == currentUser.Id && f.Id == id) + .FirstOrDefaultAsync(); + if (factor is null) return NotFound(); + + try + { + factor = await accounts.DisableAuthFactor(factor); + return Ok(factor); + } + catch (Exception ex) + { + return BadRequest(ex.Message); + } + } + + [HttpDelete("factors/{id:guid}")] + [Authorize] + public async Task> DeleteAuthFactor(Guid id) + { + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); + + var factor = await db.AccountAuthFactors + .Where(f => f.AccountId == currentUser.Id && f.Id == id) + .FirstOrDefaultAsync(); + if (factor is null) return NotFound(); + + try + { + await accounts.DeleteAuthFactor(factor); + return NoContent(); + } + catch (Exception ex) + { + return BadRequest(ex.Message); + } + } + + public class AuthorizedDevice + { + public string? Label { get; set; } + public string UserAgent { get; set; } = null!; + public string DeviceId { get; set; } = null!; + public ChallengePlatform Platform { get; set; } + public List Sessions { get; set; } = []; + } + + [HttpGet("devices")] + [Authorize] + public async Task>> GetDevices() + { + if (HttpContext.Items["CurrentUser"] is not Account currentUser || + HttpContext.Items["CurrentSession"] is not Session currentSession) return Unauthorized(); + + Response.Headers.Append("X-Auth-Session", currentSession.Id.ToString()); + + // Group sessions by the related DeviceId, then create an AuthorizedDevice for each group. + var deviceGroups = await db.AuthSessions + .Where(s => s.Account.Id == currentUser.Id) + .Include(s => s.Challenge) + .GroupBy(s => s.Challenge.DeviceId!) + .Select(g => new AuthorizedDevice + { + DeviceId = g.Key!, + UserAgent = g.First(x => x.Challenge.UserAgent != null).Challenge.UserAgent!, + Platform = g.First().Challenge.Platform!, + Label = g.Where(x => !string.IsNullOrWhiteSpace(x.Label)).Select(x => x.Label).FirstOrDefault(), + Sessions = g + .OrderByDescending(x => x.LastGrantedAt) + .ToList() + }) + .ToListAsync(); + deviceGroups = deviceGroups + .OrderByDescending(s => s.Sessions.First().LastGrantedAt) + .ToList(); + + return Ok(deviceGroups); + } + + [HttpGet("sessions")] + [Authorize] + public async Task>> GetSessions( + [FromQuery] int take = 20, + [FromQuery] int offset = 0 + ) + { + if (HttpContext.Items["CurrentUser"] is not Account currentUser || + HttpContext.Items["CurrentSession"] is not Session currentSession) return Unauthorized(); + + var query = db.AuthSessions + .Include(session => session.Account) + .Include(session => session.Challenge) + .Where(session => session.Account.Id == currentUser.Id); + + var total = await query.CountAsync(); + Response.Headers.Append("X-Total", total.ToString()); + Response.Headers.Append("X-Auth-Session", currentSession.Id.ToString()); + + var sessions = await query + .OrderByDescending(x => x.LastGrantedAt) + .Skip(offset) + .Take(take) + .ToListAsync(); + + return Ok(sessions); + } + + [HttpDelete("sessions/{id:guid}")] + [Authorize] + public async Task> DeleteSession(Guid id) + { + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); + + try + { + await accounts.DeleteSession(currentUser, id); + return NoContent(); + } + catch (Exception ex) + { + return BadRequest(ex.Message); + } + } + + [HttpDelete("sessions/current")] + [Authorize] + public async Task> DeleteCurrentSession() + { + if (HttpContext.Items["CurrentUser"] is not Account currentUser || + HttpContext.Items["CurrentSession"] is not Session currentSession) return Unauthorized(); + + try + { + await accounts.DeleteSession(currentUser, currentSession.Id); + return NoContent(); + } + catch (Exception ex) + { + return BadRequest(ex.Message); + } + } + + [HttpPatch("sessions/{id:guid}/label")] + public async Task> UpdateSessionLabel(Guid id, [FromBody] string label) + { + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); + + try + { + await accounts.UpdateSessionLabel(currentUser, id, label); + return NoContent(); + } + catch (Exception ex) + { + return BadRequest(ex.Message); + } + } + + [HttpPatch("sessions/current/label")] + public async Task> UpdateCurrentSessionLabel([FromBody] string label) + { + if (HttpContext.Items["CurrentUser"] is not Account currentUser || + HttpContext.Items["CurrentSession"] is not Session currentSession) return Unauthorized(); + + try + { + await accounts.UpdateSessionLabel(currentUser, currentSession.Id, label); + return NoContent(); + } + catch (Exception ex) + { + return BadRequest(ex.Message); + } + } + + [HttpGet("contacts")] + [Authorize] + public async Task>> GetContacts() + { + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); + + var contacts = await db.AccountContacts + .Where(c => c.AccountId == currentUser.Id) + .ToListAsync(); + + return Ok(contacts); + } + + public class AccountContactRequest + { + [Required] public AccountContactType Type { get; set; } + [Required] public string Content { get; set; } = null!; + } + + [HttpPost("contacts")] + [Authorize] + public async Task> CreateContact([FromBody] AccountContactRequest request) + { + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); + + try + { + var contact = await accounts.CreateContactMethod(currentUser, request.Type, request.Content); + return Ok(contact); + } + catch (Exception ex) + { + return BadRequest(ex.Message); + } + } + + [HttpPost("contacts/{id:guid}/verify")] + [Authorize] + public async Task> VerifyContact(Guid id) + { + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); + + var contact = await db.AccountContacts + .Where(c => c.AccountId == currentUser.Id && c.Id == id) + .FirstOrDefaultAsync(); + if (contact is null) return NotFound(); + + try + { + await accounts.VerifyContactMethod(currentUser, contact); + return Ok(contact); + } + catch (Exception ex) + { + return BadRequest(ex.Message); + } + } + + [HttpPost("contacts/{id:guid}/primary")] + [Authorize] + public async Task> SetPrimaryContact(Guid id) + { + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); + + var contact = await db.AccountContacts + .Where(c => c.AccountId == currentUser.Id && c.Id == id) + .FirstOrDefaultAsync(); + if (contact is null) return NotFound(); + + try + { + contact = await accounts.SetContactMethodPrimary(currentUser, contact); + return Ok(contact); + } + catch (Exception ex) + { + return BadRequest(ex.Message); + } + } + + [HttpDelete("contacts/{id:guid}")] + [Authorize] + public async Task> DeleteContact(Guid id) + { + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); + + var contact = await db.AccountContacts + .Where(c => c.AccountId == currentUser.Id && c.Id == id) + .FirstOrDefaultAsync(); + if (contact is null) return NotFound(); + + try + { + await accounts.DeleteContactMethod(currentUser, contact); + return NoContent(); + } + catch (Exception ex) + { + return BadRequest(ex.Message); + } + } + + [HttpGet("badges")] + [ProducesResponseType>(StatusCodes.Status200OK)] + [Authorize] + public async Task>> GetBadges() + { + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); + + var badges = await db.Badges + .Where(b => b.AccountId == currentUser.Id) + .ToListAsync(); + return Ok(badges); + } + + [HttpPost("badges/{id:guid}/active")] + [Authorize] + public async Task> ActivateBadge(Guid id) + { + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); + + try + { + await accounts.ActiveBadge(currentUser, id); + return Ok(); + } + catch (Exception ex) + { + return BadRequest(ex.Message); + } + } +} \ No newline at end of file diff --git a/DysonNetwork.Pass/Account/AccountEventService.cs b/DysonNetwork.Pass/Account/AccountEventService.cs new file mode 100644 index 0000000..b8fdf9d --- /dev/null +++ b/DysonNetwork.Pass/Account/AccountEventService.cs @@ -0,0 +1,339 @@ +using System.Globalization; +using DysonNetwork.Pass; +using DysonNetwork.Pass.Connection; +using DysonNetwork.Pass.Storage; +using DysonNetwork.Pass.Wallet; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Caching.Distributed; +using Microsoft.Extensions.Localization; +using NodaTime; +using Org.BouncyCastle.Asn1.X509; + +namespace DysonNetwork.Pass.Account; + +public class AccountEventService( + AppDatabase db, + WebSocketService ws, + ICacheService cache, + PaymentService payment, + IStringLocalizer localizer +) +{ + private static readonly Random Random = new(); + private const string StatusCacheKey = "AccountStatus_"; + + public void PurgeStatusCache(Guid userId) + { + var cacheKey = $"{StatusCacheKey}{userId}"; + cache.RemoveAsync(cacheKey); + } + + public async Task GetStatus(Guid userId) + { + var cacheKey = $"{StatusCacheKey}{userId}"; + var cachedStatus = await cache.GetAsync(cacheKey); + if (cachedStatus is not null) + { + cachedStatus!.IsOnline = !cachedStatus.IsInvisible && ws.GetAccountIsConnected(userId); + return cachedStatus; + } + + var now = SystemClock.Instance.GetCurrentInstant(); + var status = await db.AccountStatuses + .Where(e => e.AccountId == userId) + .Where(e => e.ClearedAt == null || e.ClearedAt > now) + .OrderByDescending(e => e.CreatedAt) + .FirstOrDefaultAsync(); + var isOnline = ws.GetAccountIsConnected(userId); + if (status is not null) + { + status.IsOnline = !status.IsInvisible && isOnline; + await cache.SetWithGroupsAsync(cacheKey, status, [$"{AccountService.AccountCachePrefix}{status.AccountId}"], + TimeSpan.FromMinutes(5)); + return status; + } + + if (isOnline) + { + return new Status + { + Attitude = StatusAttitude.Neutral, + IsOnline = true, + IsCustomized = false, + Label = "Online", + AccountId = userId, + }; + } + + return new Status + { + Attitude = StatusAttitude.Neutral, + IsOnline = false, + IsCustomized = false, + Label = "Offline", + AccountId = userId, + }; + } + + public async Task> GetStatuses(List userIds) + { + var results = new Dictionary(); + var cacheMissUserIds = new List(); + + foreach (var userId in userIds) + { + var cacheKey = $"{StatusCacheKey}{userId}"; + var cachedStatus = await cache.GetAsync(cacheKey); + if (cachedStatus != null) + { + cachedStatus.IsOnline = !cachedStatus.IsInvisible && ws.GetAccountIsConnected(userId); + results[userId] = cachedStatus; + } + else + { + cacheMissUserIds.Add(userId); + } + } + + if (cacheMissUserIds.Any()) + { + var now = SystemClock.Instance.GetCurrentInstant(); + var statusesFromDb = await db.AccountStatuses + .Where(e => cacheMissUserIds.Contains(e.AccountId)) + .Where(e => e.ClearedAt == null || e.ClearedAt > now) + .GroupBy(e => e.AccountId) + .Select(g => g.OrderByDescending(e => e.CreatedAt).First()) + .ToListAsync(); + + var foundUserIds = new HashSet(); + + foreach (var status in statusesFromDb) + { + var isOnline = ws.GetAccountIsConnected(status.AccountId); + status.IsOnline = !status.IsInvisible && isOnline; + results[status.AccountId] = status; + var cacheKey = $"{StatusCacheKey}{status.AccountId}"; + await cache.SetAsync(cacheKey, status, TimeSpan.FromMinutes(5)); + foundUserIds.Add(status.AccountId); + } + + var usersWithoutStatus = cacheMissUserIds.Except(foundUserIds).ToList(); + if (usersWithoutStatus.Any()) + { + foreach (var userId in usersWithoutStatus) + { + var isOnline = ws.GetAccountIsConnected(userId); + var defaultStatus = new Status + { + Attitude = StatusAttitude.Neutral, + IsOnline = isOnline, + IsCustomized = false, + Label = isOnline ? "Online" : "Offline", + AccountId = userId, + }; + results[userId] = defaultStatus; + } + } + } + + return results; + } + + public async Task CreateStatus(Account user, Status status) + { + var now = SystemClock.Instance.GetCurrentInstant(); + await db.AccountStatuses + .Where(x => x.AccountId == user.Id && (x.ClearedAt == null || x.ClearedAt > now)) + .ExecuteUpdateAsync(s => s.SetProperty(x => x.ClearedAt, now)); + + db.AccountStatuses.Add(status); + await db.SaveChangesAsync(); + + return status; + } + + public async Task ClearStatus(Account user, Status status) + { + status.ClearedAt = SystemClock.Instance.GetCurrentInstant(); + db.Update(status); + await db.SaveChangesAsync(); + PurgeStatusCache(user.Id); + } + + private const int FortuneTipCount = 7; // This will be the max index for each type (positive/negative) + private const string CaptchaCacheKey = "CheckInCaptcha_"; + private const int CaptchaProbabilityPercent = 20; + + public async Task CheckInDailyDoAskCaptcha(Account user) + { + var cacheKey = $"{CaptchaCacheKey}{user.Id}"; + var needsCaptcha = await cache.GetAsync(cacheKey); + if (needsCaptcha is not null) + return needsCaptcha!.Value; + + var result = Random.Next(100) < CaptchaProbabilityPercent; + await cache.SetAsync(cacheKey, result, TimeSpan.FromHours(24)); + return result; + } + + public async Task CheckInDailyIsAvailable(Account user) + { + var now = SystemClock.Instance.GetCurrentInstant(); + var lastCheckIn = await db.AccountCheckInResults + .Where(x => x.AccountId == user.Id) + .OrderByDescending(x => x.CreatedAt) + .FirstOrDefaultAsync(); + + if (lastCheckIn == null) + return true; + + var lastDate = lastCheckIn.CreatedAt.InUtc().Date; + var currentDate = now.InUtc().Date; + + return lastDate < currentDate; + } + + public const string CheckInLockKey = "CheckInLock_"; + + public async Task CheckInDaily(Account user) + { + var lockKey = $"{CheckInLockKey}{user.Id}"; + + try + { + var lk = await cache.AcquireLockAsync(lockKey, TimeSpan.FromMinutes(1), TimeSpan.FromMilliseconds(100)); + + if (lk != null) + await lk.ReleaseAsync(); + } + catch + { + // Ignore errors from this pre-check + } + + // Now try to acquire the lock properly + await using var lockObj = await cache.AcquireLockAsync(lockKey, TimeSpan.FromMinutes(1), TimeSpan.FromSeconds(5)); + if (lockObj is null) throw new InvalidOperationException("Check-in was in progress."); + + var cultureInfo = new CultureInfo(user.Language, false); + CultureInfo.CurrentCulture = cultureInfo; + CultureInfo.CurrentUICulture = cultureInfo; + + // Generate 2 positive tips + var positiveIndices = Enumerable.Range(1, FortuneTipCount) + .OrderBy(_ => Random.Next()) + .Take(2) + .ToList(); + var tips = positiveIndices.Select(index => new FortuneTip + { + IsPositive = true, Title = localizer[$"FortuneTipPositiveTitle_{index}"].Value, + Content = localizer[$"FortuneTipPositiveContent_{index}"].Value + }).ToList(); + + // Generate 2 negative tips + var negativeIndices = Enumerable.Range(1, FortuneTipCount) + .Except(positiveIndices) + .OrderBy(_ => Random.Next()) + .Take(2) + .ToList(); + tips.AddRange(negativeIndices.Select(index => new FortuneTip + { + IsPositive = false, Title = localizer[$"FortuneTipNegativeTitle_{index}"].Value, + Content = localizer[$"FortuneTipNegativeContent_{index}"].Value + })); + + var result = new CheckInResult + { + Tips = tips, + Level = (CheckInResultLevel)Random.Next(Enum.GetValues().Length), + AccountId = user.Id, + RewardExperience = 100, + RewardPoints = 10, + }; + + var now = SystemClock.Instance.GetCurrentInstant().InUtc().Date; + try + { + if (result.RewardPoints.HasValue) + await payment.CreateTransactionWithAccountAsync( + null, + user.Id, + WalletCurrency.SourcePoint, + result.RewardPoints.Value, + $"Check-in reward on {now:yyyy/MM/dd}" + ); + } + catch + { + result.RewardPoints = null; + } + + await db.AccountProfiles + .Where(p => p.AccountId == user.Id) + .ExecuteUpdateAsync(s => + s.SetProperty(b => b.Experience, b => b.Experience + result.RewardExperience) + ); + db.AccountCheckInResults.Add(result); + await db.SaveChangesAsync(); // Don't forget to save changes to the database + + // The lock will be automatically released by the await using statement + return result; + } + + public async Task> GetEventCalendar(Account user, int month, int year = 0, + bool replaceInvisible = false) + { + if (year == 0) + year = SystemClock.Instance.GetCurrentInstant().InUtc().Date.Year; + + // Create start and end dates for the specified month + var startOfMonth = new LocalDate(year, month, 1).AtStartOfDayInZone(DateTimeZone.Utc).ToInstant(); + var endOfMonth = startOfMonth.Plus(Duration.FromDays(DateTime.DaysInMonth(year, month))); + + var statuses = await db.AccountStatuses + .AsNoTracking() + .TagWith("GetEventCalendar_Statuses") + .Where(x => x.AccountId == user.Id && x.CreatedAt >= startOfMonth && x.CreatedAt < endOfMonth) + .Select(x => new Status + { + Id = x.Id, + Attitude = x.Attitude, + IsInvisible = !replaceInvisible && x.IsInvisible, + IsNotDisturb = x.IsNotDisturb, + Label = x.Label, + ClearedAt = x.ClearedAt, + AccountId = x.AccountId, + CreatedAt = x.CreatedAt + }) + .OrderBy(x => x.CreatedAt) + .ToListAsync(); + + var checkIn = await db.AccountCheckInResults + .AsNoTracking() + .TagWith("GetEventCalendar_CheckIn") + .Where(x => x.AccountId == user.Id && x.CreatedAt >= startOfMonth && x.CreatedAt < endOfMonth) + .ToListAsync(); + + var dates = Enumerable.Range(1, DateTime.DaysInMonth(year, month)) + .Select(day => new LocalDate(year, month, day).AtStartOfDayInZone(DateTimeZone.Utc).ToInstant()) + .ToList(); + + var statusesByDate = statuses + .GroupBy(s => s.CreatedAt.InUtc().Date) + .ToDictionary(g => g.Key, g => g.ToList()); + + var checkInByDate = checkIn + .ToDictionary(c => c.CreatedAt.InUtc().Date); + + return dates.Select(date => + { + var utcDate = date.InUtc().Date; + return new DailyEventResponse + { + Date = date, + CheckInResult = checkInByDate.GetValueOrDefault(utcDate), + Statuses = statusesByDate.GetValueOrDefault(utcDate, new List()) + }; + }).ToList(); + } +} \ No newline at end of file diff --git a/DysonNetwork.Pass/Account/AccountService.cs b/DysonNetwork.Pass/Account/AccountService.cs new file mode 100644 index 0000000..ed80166 --- /dev/null +++ b/DysonNetwork.Pass/Account/AccountService.cs @@ -0,0 +1,657 @@ +using System.Globalization; +using DysonNetwork.Pass; +using DysonNetwork.Pass.Auth.OpenId; +using DysonNetwork.Pass.Email; + +using DysonNetwork.Pass.Localization; +using DysonNetwork.Pass.Permission; +using DysonNetwork.Pass.Storage; +using EFCore.BulkExtensions; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Localization; +using NodaTime; +using Org.BouncyCastle.Utilities; +using OtpNet; + +namespace DysonNetwork.Pass.Account; + +public class AccountService( + AppDatabase db, + MagicSpellService spells, + AccountUsernameService uname, + NotificationService nty, + EmailService mailer, + IStringLocalizer localizer, + ICacheService cache, + ILogger logger +) +{ + public static void SetCultureInfo(Account account) + { + SetCultureInfo(account.Language); + } + + public static void SetCultureInfo(string? languageCode) + { + var info = new CultureInfo(languageCode ?? "en-us", false); + CultureInfo.CurrentCulture = info; + CultureInfo.CurrentUICulture = info; + } + + public const string AccountCachePrefix = "account:"; + + public async Task PurgeAccountCache(Account account) + { + await cache.RemoveGroupAsync($"{AccountCachePrefix}{account.Id}"); + } + + public async Task LookupAccount(string probe) + { + var account = await db.Accounts.Where(a => a.Name == probe).FirstOrDefaultAsync(); + if (account is not null) return account; + + var contact = await db.AccountContacts + .Where(c => c.Content == probe) + .Include(c => c.Account) + .FirstOrDefaultAsync(); + return contact?.Account; + } + + public async Task LookupAccountByConnection(string identifier, string provider) + { + var connection = await db.AccountConnections + .Where(c => c.ProvidedIdentifier == identifier && c.Provider == provider) + .Include(c => c.Account) + .FirstOrDefaultAsync(); + return connection?.Account; + } + + public async Task GetAccountLevel(Guid accountId) + { + var profile = await db.AccountProfiles + .Where(a => a.AccountId == accountId) + .FirstOrDefaultAsync(); + return profile?.Level; + } + + public async Task CreateAccount( + string name, + string nick, + string email, + string? password, + string language = "en-US", + bool isEmailVerified = false, + bool isActivated = false + ) + { + await using var transaction = await db.Database.BeginTransactionAsync(); + try + { + var dupeNameCount = await db.Accounts.Where(a => a.Name == name).CountAsync(); + if (dupeNameCount > 0) + throw new InvalidOperationException("Account name has already been taken."); + + var account = new Account + { + Name = name, + Nick = nick, + Language = language, + Contacts = new List + { + new() + { + Type = AccountContactType.Email, + Content = email, + VerifiedAt = isEmailVerified ? SystemClock.Instance.GetCurrentInstant() : null, + IsPrimary = true + } + }, + AuthFactors = password is not null + ? new List + { + new AccountAuthFactor + { + Type = AccountAuthFactorType.Password, + Secret = password, + EnabledAt = SystemClock.Instance.GetCurrentInstant() + }.HashSecret() + } + : [], + Profile = new Profile() + }; + + if (isActivated) + { + account.ActivatedAt = SystemClock.Instance.GetCurrentInstant(); + var defaultGroup = await db.PermissionGroups.FirstOrDefaultAsync(g => g.Key == "default"); + if (defaultGroup is not null) + { + db.PermissionGroupMembers.Add(new PermissionGroupMember + { + Actor = $"user:{account.Id}", + Group = defaultGroup + }); + } + } + else + { + var spell = await spells.CreateMagicSpell( + account, + MagicSpellType.AccountActivation, + new Dictionary + { + { "contact_method", account.Contacts.First().Content } + } + ); + await spells.NotifyMagicSpell(spell, true); + } + + db.Accounts.Add(account); + await db.SaveChangesAsync(); + + await transaction.CommitAsync(); + return account; + } + catch + { + await transaction.RollbackAsync(); + throw; + } + } + + public async Task CreateAccount(OidcUserInfo userInfo) + { + if (string.IsNullOrEmpty(userInfo.Email)) + throw new ArgumentException("Email is required for account creation"); + + var displayName = !string.IsNullOrEmpty(userInfo.DisplayName) + ? userInfo.DisplayName + : $"{userInfo.FirstName} {userInfo.LastName}".Trim(); + + // Generate username from email + var username = await uname.GenerateUsernameFromEmailAsync(userInfo.Email); + + return await CreateAccount( + username, + displayName, + userInfo.Email, + null, + "en-US", + userInfo.EmailVerified, + userInfo.EmailVerified + ); + } + + public async Task RequestAccountDeletion(Account account) + { + var spell = await spells.CreateMagicSpell( + account, + MagicSpellType.AccountRemoval, + new Dictionary(), + SystemClock.Instance.GetCurrentInstant().Plus(Duration.FromHours(24)), + preventRepeat: true + ); + await spells.NotifyMagicSpell(spell); + } + + public async Task RequestPasswordReset(Account account) + { + var spell = await spells.CreateMagicSpell( + account, + MagicSpellType.AuthPasswordReset, + new Dictionary(), + SystemClock.Instance.GetCurrentInstant().Plus(Duration.FromHours(24)), + preventRepeat: true + ); + await spells.NotifyMagicSpell(spell); + } + + public async Task CheckAuthFactorExists(Account account, AccountAuthFactorType type) + { + var isExists = await db.AccountAuthFactors + .Where(x => x.AccountId == account.Id && x.Type == type) + .AnyAsync(); + return isExists; + } + + public async Task CreateAuthFactor(Account account, AccountAuthFactorType type, string? secret) + { + AccountAuthFactor? factor = null; + switch (type) + { + case AccountAuthFactorType.Password: + if (string.IsNullOrWhiteSpace(secret)) throw new ArgumentNullException(nameof(secret)); + factor = new AccountAuthFactor + { + Type = AccountAuthFactorType.Password, + Trustworthy = 1, + AccountId = account.Id, + Secret = secret, + EnabledAt = SystemClock.Instance.GetCurrentInstant(), + }.HashSecret(); + break; + case AccountAuthFactorType.EmailCode: + factor = new AccountAuthFactor + { + Type = AccountAuthFactorType.EmailCode, + Trustworthy = 2, + EnabledAt = SystemClock.Instance.GetCurrentInstant(), + }; + break; + case AccountAuthFactorType.InAppCode: + factor = new AccountAuthFactor + { + Type = AccountAuthFactorType.InAppCode, + Trustworthy = 1, + EnabledAt = SystemClock.Instance.GetCurrentInstant() + }; + break; + case AccountAuthFactorType.TimedCode: + var skOtp = KeyGeneration.GenerateRandomKey(20); + var skOtp32 = Base32Encoding.ToString(skOtp); + factor = new AccountAuthFactor + { + Secret = skOtp32, + Type = AccountAuthFactorType.TimedCode, + Trustworthy = 2, + EnabledAt = null, // It needs to be tired once to enable + CreatedResponse = new Dictionary + { + ["uri"] = new OtpUri( + OtpType.Totp, + skOtp32, + account.Id.ToString(), + "Solar Network" + ).ToString(), + } + }; + break; + case AccountAuthFactorType.PinCode: + if (string.IsNullOrWhiteSpace(secret)) throw new ArgumentNullException(nameof(secret)); + if (!secret.All(char.IsDigit) || secret.Length != 6) + throw new ArgumentException("PIN code must be exactly 6 digits"); + factor = new AccountAuthFactor + { + Type = AccountAuthFactorType.PinCode, + Trustworthy = 0, // Only for confirming, can't be used for login + Secret = secret, + EnabledAt = SystemClock.Instance.GetCurrentInstant(), + }.HashSecret(); + break; + default: + throw new ArgumentOutOfRangeException(nameof(type), type, null); + } + + if (factor is null) throw new InvalidOperationException("Unable to create auth factor."); + factor.AccountId = account.Id; + db.AccountAuthFactors.Add(factor); + await db.SaveChangesAsync(); + return factor; + } + + public async Task EnableAuthFactor(AccountAuthFactor factor, string? code) + { + if (factor.EnabledAt is not null) throw new ArgumentException("The factor has been enabled."); + if (factor.Type is AccountAuthFactorType.Password or AccountAuthFactorType.TimedCode) + { + if (code is null || !factor.VerifyPassword(code)) + throw new InvalidOperationException( + "Invalid code, you need to enter the correct code to enable the factor." + ); + } + + factor.EnabledAt = SystemClock.Instance.GetCurrentInstant(); + db.Update(factor); + await db.SaveChangesAsync(); + + return factor; + } + + public async Task DisableAuthFactor(AccountAuthFactor factor) + { + if (factor.EnabledAt is null) throw new ArgumentException("The factor has been disabled."); + + var count = await db.AccountAuthFactors + .Where(f => f.AccountId == factor.AccountId && f.EnabledAt != null) + .CountAsync(); + if (count <= 1) + throw new InvalidOperationException( + "Disabling this auth factor will cause you have no active auth factors."); + + factor.EnabledAt = null; + db.Update(factor); + await db.SaveChangesAsync(); + + return factor; + } + + public async Task DeleteAuthFactor(AccountAuthFactor factor) + { + var count = await db.AccountAuthFactors + .Where(f => f.AccountId == factor.AccountId) + .If(factor.EnabledAt is not null, q => q.Where(f => f.EnabledAt != null)) + .CountAsync(); + if (count <= 1) + throw new InvalidOperationException("Deleting this auth factor will cause you have no auth factor."); + + db.AccountAuthFactors.Remove(factor); + await db.SaveChangesAsync(); + } + + /// + /// Send the auth factor verification code to users, for factors like in-app code and email. + /// Sometimes it requires a hint, like a part of the user's email address to ensure the user is who own the account. + /// + /// The owner of the auth factor + /// The auth factor needed to send code + /// The part of the contact method for verification + public async Task SendFactorCode(Account account, AccountAuthFactor factor, string? hint = null) + { + var code = new Random().Next(100000, 999999).ToString("000000"); + + switch (factor.Type) + { + case AccountAuthFactorType.InAppCode: + if (await _GetFactorCode(factor) is not null) + throw new InvalidOperationException("A factor code has been sent and in active duration."); + + await nty.SendNotification( + account, + "auth.verification", + localizer["AuthCodeTitle"], + null, + localizer["AuthCodeBody", code], + save: true + ); + await _SetFactorCode(factor, code, TimeSpan.FromMinutes(5)); + break; + case AccountAuthFactorType.EmailCode: + if (await _GetFactorCode(factor) is not null) + throw new InvalidOperationException("A factor code has been sent and in active duration."); + + ArgumentNullException.ThrowIfNull(hint); + hint = hint.Replace("@", "").Replace(".", "").Replace("+", "").Replace("%", ""); + if (string.IsNullOrWhiteSpace(hint)) + { + logger.LogWarning( + "Unable to send factor code to #{FactorId} with hint {Hint}, due to invalid hint...", + factor.Id, + hint + ); + return; + } + + var contact = await db.AccountContacts + .Where(c => c.Type == AccountContactType.Email) + .Where(c => c.VerifiedAt != null) + .Where(c => EF.Functions.ILike(c.Content, $"%{hint}%")) + .Include(c => c.Account) + .FirstOrDefaultAsync(); + if (contact is null) + { + logger.LogWarning( + "Unable to send factor code to #{FactorId} with hint {Hint}, due to no contact method found according to hint...", + factor.Id, + hint + ); + return; + } + + await mailer.SendTemplatedEmailAsync( + account.Nick, + contact.Content, + localizer["VerificationEmail"], + new VerificationEmailModel + { + Name = account.Name, + Code = code + } + ); + + await _SetFactorCode(factor, code, TimeSpan.FromMinutes(30)); + break; + case AccountAuthFactorType.Password: + case AccountAuthFactorType.TimedCode: + default: + // No need to send, such as password etc... + return; + } + } + + public async Task VerifyFactorCode(AccountAuthFactor factor, string code) + { + switch (factor.Type) + { + case AccountAuthFactorType.EmailCode: + case AccountAuthFactorType.InAppCode: + var correctCode = await _GetFactorCode(factor); + var isCorrect = correctCode is not null && + string.Equals(correctCode, code, StringComparison.OrdinalIgnoreCase); + await cache.RemoveAsync($"{AuthFactorCachePrefix}{factor.Id}:code"); + return isCorrect; + case AccountAuthFactorType.Password: + case AccountAuthFactorType.TimedCode: + default: + return factor.VerifyPassword(code); + } + } + + private const string AuthFactorCachePrefix = "authfactor:"; + + private async Task _SetFactorCode(AccountAuthFactor factor, string code, TimeSpan expires) + { + await cache.SetAsync( + $"{AuthFactorCachePrefix}{factor.Id}:code", + code, + expires + ); + } + + private async Task _GetFactorCode(AccountAuthFactor factor) + { + return await cache.GetAsync( + $"{AuthFactorCachePrefix}{factor.Id}:code" + ); + } + + public async Task UpdateSessionLabel(Account account, Guid sessionId, string label) + { + var session = await db.AuthSessions + .Include(s => s.Challenge) + .Where(s => s.Id == sessionId && s.AccountId == account.Id) + .FirstOrDefaultAsync(); + if (session is null) throw new InvalidOperationException("Session was not found."); + + await db.AuthSessions + .Include(s => s.Challenge) + .Where(s => s.Challenge.DeviceId == session.Challenge.DeviceId) + .ExecuteUpdateAsync(p => p.SetProperty(s => s.Label, label)); + + var sessions = await db.AuthSessions + .Include(s => s.Challenge) + .Where(s => s.AccountId == session.Id && s.Challenge.DeviceId == session.Challenge.DeviceId) + .ToListAsync(); + foreach (var item in sessions) + await cache.RemoveAsync($"{DysonTokenAuthHandler.AuthCachePrefix}{item.Id}"); + + return session; + } + + public async Task DeleteSession(Account account, Guid sessionId) + { + var session = await db.AuthSessions + .Include(s => s.Challenge) + .Where(s => s.Id == sessionId && s.AccountId == account.Id) + .FirstOrDefaultAsync(); + if (session is null) throw new InvalidOperationException("Session was not found."); + + var sessions = await db.AuthSessions + .Include(s => s.Challenge) + .Where(s => s.AccountId == session.Id && s.Challenge.DeviceId == session.Challenge.DeviceId) + .ToListAsync(); + + if (session.Challenge.DeviceId is not null) + await nty.UnsubscribePushNotifications(session.Challenge.DeviceId); + + // The current session should be included in the sessions' list + await db.AuthSessions + .Include(s => s.Challenge) + .Where(s => s.Challenge.DeviceId == session.Challenge.DeviceId) + .ExecuteDeleteAsync(); + + foreach (var item in sessions) + await cache.RemoveAsync($"{DysonTokenAuthHandler.AuthCachePrefix}{item.Id}"); + } + + public async Task CreateContactMethod(Account account, AccountContactType type, string content) + { + var contact = new AccountContact + { + Type = type, + Content = content, + AccountId = account.Id, + }; + + db.AccountContacts.Add(contact); + await db.SaveChangesAsync(); + + return contact; + } + + public async Task VerifyContactMethod(Account account, AccountContact contact) + { + var spell = await spells.CreateMagicSpell( + account, + MagicSpellType.ContactVerification, + new Dictionary { { "contact_method", contact.Content } }, + expiredAt: SystemClock.Instance.GetCurrentInstant().Plus(Duration.FromHours(24)), + preventRepeat: true + ); + await spells.NotifyMagicSpell(spell); + } + + public async Task SetContactMethodPrimary(Account account, AccountContact contact) + { + if (contact.AccountId != account.Id) + throw new InvalidOperationException("Contact method does not belong to this account."); + if (contact.VerifiedAt is null) + throw new InvalidOperationException("Cannot set unverified contact method as primary."); + + await using var transaction = await db.Database.BeginTransactionAsync(); + + try + { + await db.AccountContacts + .Where(c => c.AccountId == account.Id && c.Type == contact.Type) + .ExecuteUpdateAsync(s => s.SetProperty(x => x.IsPrimary, false)); + + contact.IsPrimary = true; + db.AccountContacts.Update(contact); + await db.SaveChangesAsync(); + + await transaction.CommitAsync(); + return contact; + } + catch + { + await transaction.RollbackAsync(); + throw; + } + } + + public async Task DeleteContactMethod(Account account, AccountContact contact) + { + if (contact.AccountId != account.Id) + throw new InvalidOperationException("Contact method does not belong to this account."); + if (contact.IsPrimary) + throw new InvalidOperationException("Cannot delete primary contact method."); + + db.AccountContacts.Remove(contact); + await db.SaveChangesAsync(); + } + + /// + /// This method will grant a badge to the account. + /// Shouldn't be exposed to normal user and the user itself. + /// + public async Task GrantBadge(Account account, Badge badge) + { + badge.AccountId = account.Id; + db.Badges.Add(badge); + await db.SaveChangesAsync(); + return badge; + } + + /// + /// This method will revoke a badge from the account. + /// Shouldn't be exposed to normal user and the user itself. + /// + public async Task RevokeBadge(Account account, Guid badgeId) + { + var badge = await db.Badges + .Where(b => b.AccountId == account.Id && b.Id == badgeId) + .OrderByDescending(b => b.CreatedAt) + .FirstOrDefaultAsync(); + if (badge is null) throw new InvalidOperationException("Badge was not found."); + + var profile = await db.AccountProfiles + .Where(p => p.AccountId == account.Id) + .FirstOrDefaultAsync(); + if (profile?.ActiveBadge is not null && profile.ActiveBadge.Id == badge.Id) + profile.ActiveBadge = null; + + db.Remove(badge); + await db.SaveChangesAsync(); + } + + public async Task ActiveBadge(Account account, Guid badgeId) + { + await using var transaction = await db.Database.BeginTransactionAsync(); + + try + { + var badge = await db.Badges + .Where(b => b.AccountId == account.Id && b.Id == badgeId) + .OrderByDescending(b => b.CreatedAt) + .FirstOrDefaultAsync(); + if (badge is null) throw new InvalidOperationException("Badge was not found."); + + await db.Badges + .Where(b => b.AccountId == account.Id && b.Id != badgeId) + .ExecuteUpdateAsync(s => s.SetProperty(p => p.ActivatedAt, p => null)); + + badge.ActivatedAt = SystemClock.Instance.GetCurrentInstant(); + db.Update(badge); + await db.SaveChangesAsync(); + + await db.AccountProfiles + .Where(p => p.AccountId == account.Id) + .ExecuteUpdateAsync(s => s.SetProperty(p => p.ActiveBadge, badge.ToReference())); + await PurgeAccountCache(account); + + await transaction.CommitAsync(); + } + catch + { + await transaction.RollbackAsync(); + throw; + } + } + + /// + /// The maintenance method for server administrator. + /// To check every user has an account profile and to create them if it isn't having one. + /// + public async Task EnsureAccountProfileCreated() + { + var accountsId = await db.Accounts.Select(a => a.Id).ToListAsync(); + var existingId = await db.AccountProfiles.Select(p => p.AccountId).ToListAsync(); + var missingId = accountsId.Except(existingId).ToList(); + + if (missingId.Count != 0) + { + var newProfiles = missingId.Select(id => new Profile { Id = Guid.NewGuid(), AccountId = id }).ToList(); + await db.BulkInsertAsync(newProfiles); + } + } +} \ No newline at end of file diff --git a/DysonNetwork.Pass/Account/AccountUsernameService.cs b/DysonNetwork.Pass/Account/AccountUsernameService.cs new file mode 100644 index 0000000..25ffcaa --- /dev/null +++ b/DysonNetwork.Pass/Account/AccountUsernameService.cs @@ -0,0 +1,105 @@ +using System.Text.RegularExpressions; +using Microsoft.EntityFrameworkCore; + +namespace DysonNetwork.Pass.Account; + +/// +/// Service for handling username generation and validation +/// +public class AccountUsernameService(AppDatabase db) +{ + private readonly Random _random = new(); + + /// + /// Generates a unique username based on the provided base name + /// + /// The preferred username + /// A unique username + public async Task GenerateUniqueUsernameAsync(string baseName) + { + // Sanitize the base name + var sanitized = SanitizeUsername(baseName); + + // If the base name is empty after sanitization, use a default + if (string.IsNullOrEmpty(sanitized)) + { + sanitized = "user"; + } + + // Check if the sanitized name is available + if (!await IsUsernameExistsAsync(sanitized)) + { + return sanitized; + } + + // Try up to 10 times with random numbers + for (int i = 0; i < 10; i++) + { + var suffix = _random.Next(1000, 9999); + var candidate = $"{sanitized}{suffix}"; + + if (!await IsUsernameExistsAsync(candidate)) + { + return candidate; + } + } + + // If all attempts fail, use a timestamp + var timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); + return $"{sanitized}{timestamp}"; + } + + /// + /// Sanitizes a username by removing invalid characters and converting to lowercase + /// + public string SanitizeUsername(string username) + { + if (string.IsNullOrEmpty(username)) + return string.Empty; + + // Replace spaces and special characters with underscores + var sanitized = Regex.Replace(username, @"[^a-zA-Z0-9_\-]", ""); + + // Convert to lowercase + sanitized = sanitized.ToLowerInvariant(); + + // Ensure it starts with a letter + if (sanitized.Length > 0 && !char.IsLetter(sanitized[0])) + { + sanitized = "u" + sanitized; + } + + // Truncate if too long + if (sanitized.Length > 30) + { + sanitized = sanitized[..30]; + } + + return sanitized; + } + + /// + /// Checks if a username already exists + /// + public async Task IsUsernameExistsAsync(string username) + { + return await db.Accounts.AnyAsync(a => a.Name == username); + } + + /// + /// Generates a username from an email address + /// + /// The email address to generate a username from + /// A unique username derived from the email + public async Task GenerateUsernameFromEmailAsync(string email) + { + if (string.IsNullOrEmpty(email)) + return await GenerateUniqueUsernameAsync("user"); + + // Extract the local part of the email (before the @) + var localPart = email.Split('@')[0]; + + // Use the local part as the base for username generation + return await GenerateUniqueUsernameAsync(localPart); + } +} \ No newline at end of file diff --git a/DysonNetwork.Pass/Account/ActionLog.cs b/DysonNetwork.Pass/Account/ActionLog.cs new file mode 100644 index 0000000..03a4d02 --- /dev/null +++ b/DysonNetwork.Pass/Account/ActionLog.cs @@ -0,0 +1,58 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using Point = NetTopologySuite.Geometries.Point; + +namespace DysonNetwork.Pass.Account; + +public abstract class ActionLogType +{ + public const string NewLogin = "login"; + public const string ChallengeAttempt = "challenges.attempt"; + public const string ChallengeSuccess = "challenges.success"; + public const string ChallengeFailure = "challenges.failure"; + public const string PostCreate = "posts.create"; + public const string PostUpdate = "posts.update"; + public const string PostDelete = "posts.delete"; + public const string PostReact = "posts.react"; + public const string MessageCreate = "messages.create"; + public const string MessageUpdate = "messages.update"; + public const string MessageDelete = "messages.delete"; + public const string MessageReact = "messages.react"; + public const string PublisherCreate = "publishers.create"; + public const string PublisherUpdate = "publishers.update"; + public const string PublisherDelete = "publishers.delete"; + public const string PublisherMemberInvite = "publishers.members.invite"; + public const string PublisherMemberJoin = "publishers.members.join"; + public const string PublisherMemberLeave = "publishers.members.leave"; + public const string PublisherMemberKick = "publishers.members.kick"; + public const string RealmCreate = "realms.create"; + public const string RealmUpdate = "realms.update"; + public const string RealmDelete = "realms.delete"; + public const string RealmInvite = "realms.invite"; + public const string RealmJoin = "realms.join"; + public const string RealmLeave = "realms.leave"; + public const string RealmKick = "realms.kick"; + public const string RealmAdjustRole = "realms.role.edit"; + public const string ChatroomCreate = "chatrooms.create"; + public const string ChatroomUpdate = "chatrooms.update"; + public const string ChatroomDelete = "chatrooms.delete"; + public const string ChatroomInvite = "chatrooms.invite"; + public const string ChatroomJoin = "chatrooms.join"; + public const string ChatroomLeave = "chatrooms.leave"; + public const string ChatroomKick = "chatrooms.kick"; + public const string ChatroomAdjustRole = "chatrooms.role.edit"; +} + +public class ActionLog : ModelBase +{ + public Guid Id { get; set; } = Guid.NewGuid(); + [MaxLength(4096)] public string Action { get; set; } = null!; + [Column(TypeName = "jsonb")] public Dictionary Meta { get; set; } = new(); + [MaxLength(512)] public string? UserAgent { get; set; } + [MaxLength(128)] public string? IpAddress { get; set; } + public Point? Location { get; set; } + + public Guid AccountId { get; set; } + public Account Account { get; set; } = null!; + public Guid? SessionId { get; set; } +} \ No newline at end of file diff --git a/DysonNetwork.Pass/Account/ActionLogService.cs b/DysonNetwork.Pass/Account/ActionLogService.cs new file mode 100644 index 0000000..a85ff6d --- /dev/null +++ b/DysonNetwork.Pass/Account/ActionLogService.cs @@ -0,0 +1,46 @@ +using Quartz; +using DysonNetwork.Pass; +using DysonNetwork.Pass.Storage; +using DysonNetwork.Pass.Storage.Handlers; + +namespace DysonNetwork.Pass.Account; + +public class ActionLogService(GeoIpService geo, FlushBufferService fbs) +{ + public void CreateActionLog(Guid accountId, string action, Dictionary meta) + { + var log = new ActionLog + { + Action = action, + AccountId = accountId, + Meta = meta, + }; + + fbs.Enqueue(log); + } + + public void CreateActionLogFromRequest(string action, Dictionary meta, HttpRequest request, + Account? account = null) + { + var log = new ActionLog + { + Action = action, + Meta = meta, + UserAgent = request.Headers.UserAgent, + IpAddress = request.HttpContext.Connection.RemoteIpAddress?.ToString(), + Location = geo.GetPointFromIp(request.HttpContext.Connection.RemoteIpAddress?.ToString()) + }; + + if (request.HttpContext.Items["CurrentUser"] is Account currentUser) + log.AccountId = currentUser.Id; + else if (account != null) + log.AccountId = account.Id; + else + throw new ArgumentException("No user context was found"); + + if (request.HttpContext.Items["CurrentSession"] is Auth.Session currentSession) + log.SessionId = currentSession.Id; + + fbs.Enqueue(log); + } +} \ No newline at end of file diff --git a/DysonNetwork.Pass/Account/Badge.cs b/DysonNetwork.Pass/Account/Badge.cs new file mode 100644 index 0000000..7f15899 --- /dev/null +++ b/DysonNetwork.Pass/Account/Badge.cs @@ -0,0 +1,47 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Text.Json.Serialization; +using NodaTime; + +namespace DysonNetwork.Pass.Account; + +public class Badge : ModelBase +{ + public Guid Id { get; set; } = Guid.NewGuid(); + [MaxLength(1024)] public string Type { get; set; } = null!; + [MaxLength(1024)] public string? Label { get; set; } + [MaxLength(4096)] public string? Caption { get; set; } + [Column(TypeName = "jsonb")] public Dictionary Meta { get; set; } = new(); + public Instant? ActivatedAt { get; set; } + public Instant? ExpiredAt { get; set; } + + public Guid AccountId { get; set; } + [JsonIgnore] public Account Account { get; set; } = null!; + + public BadgeReferenceObject ToReference() + { + return new BadgeReferenceObject + { + Id = Id, + Type = Type, + Label = Label, + Caption = Caption, + Meta = Meta, + ActivatedAt = ActivatedAt, + ExpiredAt = ExpiredAt, + AccountId = AccountId + }; + } +} + +public class BadgeReferenceObject : ModelBase +{ + public Guid Id { get; set; } + public string Type { get; set; } = null!; + public string? Label { get; set; } + public string? Caption { get; set; } + public Dictionary? Meta { get; set; } + public Instant? ActivatedAt { get; set; } + public Instant? ExpiredAt { get; set; } + public Guid AccountId { get; set; } +} \ No newline at end of file diff --git a/DysonNetwork.Pass/Account/Event.cs b/DysonNetwork.Pass/Account/Event.cs new file mode 100644 index 0000000..cbf63f5 --- /dev/null +++ b/DysonNetwork.Pass/Account/Event.cs @@ -0,0 +1,65 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using NodaTime; + +namespace DysonNetwork.Pass.Account; + +public enum StatusAttitude +{ + Positive, + Negative, + Neutral +} + +public class Status : ModelBase +{ + public Guid Id { get; set; } = Guid.NewGuid(); + public StatusAttitude Attitude { get; set; } + [NotMapped] public bool IsOnline { get; set; } + [NotMapped] public bool IsCustomized { get; set; } = true; + public bool IsInvisible { get; set; } + public bool IsNotDisturb { get; set; } + [MaxLength(1024)] public string? Label { get; set; } + public Instant? ClearedAt { get; set; } + + public Guid AccountId { get; set; } + public Account Account { get; set; } = null!; +} + +public enum CheckInResultLevel +{ + Worst, + Worse, + Normal, + Better, + Best +} + +public class CheckInResult : ModelBase +{ + public Guid Id { get; set; } = Guid.NewGuid(); + public CheckInResultLevel Level { get; set; } + public decimal? RewardPoints { get; set; } + public int? RewardExperience { get; set; } + [Column(TypeName = "jsonb")] public ICollection Tips { get; set; } = new List(); + + public Guid AccountId { get; set; } + public Account Account { get; set; } = null!; +} + +public class FortuneTip +{ + public bool IsPositive { get; set; } + public string Title { get; set; } = null!; + public string Content { get; set; } = null!; +} + +/// +/// This method should not be mapped. Used to generate the daily event calendar. +/// +public class DailyEventResponse +{ + public Instant Date { get; set; } + public CheckInResult? CheckInResult { get; set; } + public ICollection Statuses { get; set; } = new List(); +} \ No newline at end of file diff --git a/DysonNetwork.Pass/Account/MagicSpell.cs b/DysonNetwork.Pass/Account/MagicSpell.cs new file mode 100644 index 0000000..37f19be --- /dev/null +++ b/DysonNetwork.Pass/Account/MagicSpell.cs @@ -0,0 +1,30 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Text.Json.Serialization; +using Microsoft.EntityFrameworkCore; +using NodaTime; + +namespace DysonNetwork.Pass.Account; + +public enum MagicSpellType +{ + AccountActivation, + AccountDeactivation, + AccountRemoval, + AuthPasswordReset, + ContactVerification, +} + +[Index(nameof(Spell), IsUnique = true)] +public class MagicSpell : ModelBase +{ + public Guid Id { get; set; } = Guid.NewGuid(); + [JsonIgnore] [MaxLength(1024)] public string Spell { get; set; } = null!; + public MagicSpellType Type { get; set; } + public Instant? ExpiresAt { get; set; } + public Instant? AffectedAt { get; set; } + [Column(TypeName = "jsonb")] public Dictionary Meta { get; set; } = new(); + + public Guid? AccountId { get; set; } + public Account? Account { get; set; } +} \ No newline at end of file diff --git a/DysonNetwork.Pass/Account/MagicSpellController.cs b/DysonNetwork.Pass/Account/MagicSpellController.cs new file mode 100644 index 0000000..ec1a905 --- /dev/null +++ b/DysonNetwork.Pass/Account/MagicSpellController.cs @@ -0,0 +1,19 @@ +using Microsoft.AspNetCore.Mvc; + +namespace DysonNetwork.Pass.Account; + +[ApiController] +[Route("/api/spells")] +public class MagicSpellController(AppDatabase db, MagicSpellService sp) : ControllerBase +{ + [HttpPost("{spellId:guid}/resend")] + public async Task ResendMagicSpell(Guid spellId) + { + var spell = db.MagicSpells.FirstOrDefault(x => x.Id == spellId); + if (spell == null) + return NotFound(); + + await sp.NotifyMagicSpell(spell, true); + return Ok(); + } +} \ No newline at end of file diff --git a/DysonNetwork.Pass/Account/MagicSpellService.cs b/DysonNetwork.Pass/Account/MagicSpellService.cs new file mode 100644 index 0000000..6140c1f --- /dev/null +++ b/DysonNetwork.Pass/Account/MagicSpellService.cs @@ -0,0 +1,252 @@ +using System.Globalization; +using System.Security.Cryptography; +using System.Text.Json; +using DysonNetwork.Pass; +using DysonNetwork.Pass.Pages.Emails; +using DysonNetwork.Pass.Permission; +using DysonNetwork.Pass.Resources.Localization; +using DysonNetwork.Pass.Resources.Pages.Emails; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Localization; +using NodaTime; + +namespace DysonNetwork.Pass.Account; + +public class MagicSpellService( + AppDatabase db, + EmailService email, + IConfiguration configuration, + ILogger logger, + IStringLocalizer localizer +) +{ + public async Task CreateMagicSpell( + Account account, + MagicSpellType type, + Dictionary meta, + Instant? expiredAt = null, + Instant? affectedAt = null, + bool preventRepeat = false + ) + { + if (preventRepeat) + { + var now = SystemClock.Instance.GetCurrentInstant(); + var existingSpell = await db.MagicSpells + .Where(s => s.AccountId == account.Id) + .Where(s => s.Type == type) + .Where(s => s.ExpiresAt == null || s.ExpiresAt > now) + .FirstOrDefaultAsync(); + + if (existingSpell != null) + { + throw new InvalidOperationException($"Account already has an active magic spell of type {type}"); + } + } + + var spellWord = _GenerateRandomString(128); + var spell = new MagicSpell + { + Spell = spellWord, + Type = type, + ExpiresAt = expiredAt, + AffectedAt = affectedAt, + AccountId = account.Id, + Meta = meta + }; + + db.MagicSpells.Add(spell); + await db.SaveChangesAsync(); + + return spell; + } + + public async Task NotifyMagicSpell(MagicSpell spell, bool bypassVerify = false) + { + var contact = await db.AccountContacts + .Where(c => c.Account.Id == spell.AccountId) + .Where(c => c.Type == AccountContactType.Email) + .Where(c => c.VerifiedAt != null || bypassVerify) + .OrderByDescending(c => c.IsPrimary) + .Include(c => c.Account) + .FirstOrDefaultAsync(); + if (contact is null) throw new ArgumentException("Account has no contact method that can use"); + + var link = $"{configuration.GetValue("BaseUrl")}/spells/{Uri.EscapeDataString(spell.Spell)}"; + + logger.LogInformation("Sending magic spell... {Link}", link); + + var accountLanguage = await db.Accounts + .Where(a => a.Id == spell.AccountId) + .Select(a => a.Language) + .FirstOrDefaultAsync(); + AccountService.SetCultureInfo(accountLanguage); + + try + { + switch (spell.Type) + { + case MagicSpellType.AccountActivation: + await email.SendTemplatedEmailAsync( + contact.Account.Nick, + contact.Content, + localizer["EmailLandingTitle"], + new LandingEmailModel + { + Name = contact.Account.Name, + Link = link + } + ); + break; + case MagicSpellType.AccountRemoval: + await email.SendTemplatedEmailAsync( + contact.Account.Nick, + contact.Content, + localizer["EmailAccountDeletionTitle"], + new AccountDeletionEmailModel + { + Name = contact.Account.Name, + Link = link + } + ); + break; + case MagicSpellType.AuthPasswordReset: + await email.SendTemplatedEmailAsync( + contact.Account.Nick, + contact.Content, + localizer["EmailAccountDeletionTitle"], + new PasswordResetEmailModel + { + Name = contact.Account.Name, + Link = link + } + ); + break; + case MagicSpellType.ContactVerification: + if (spell.Meta["contact_method"] is not string contactMethod) + throw new InvalidOperationException("Contact method is not found."); + await email.SendTemplatedEmailAsync( + contact.Account.Nick, + contactMethod!, + localizer["EmailContactVerificationTitle"], + new ContactVerificationEmailModel + { + Name = contact.Account.Name, + Link = link + } + ); + break; + default: + throw new ArgumentOutOfRangeException(); + } + } + catch (Exception err) + { + logger.LogError($"Error sending magic spell (${spell.Spell})... {err}"); + } + } + + public async Task ApplyMagicSpell(MagicSpell spell) + { + switch (spell.Type) + { + case MagicSpellType.AuthPasswordReset: + throw new ArgumentException( + "For password reset spell, please use the ApplyPasswordReset method instead." + ); + case MagicSpellType.AccountRemoval: + var account = await db.Accounts.FirstOrDefaultAsync(c => c.Id == spell.AccountId); + if (account is null) break; + db.Accounts.Remove(account); + break; + case MagicSpellType.AccountActivation: + var contactMethod = (spell.Meta["contact_method"] as JsonElement? ?? default).ToString(); + var contact = await + db.AccountContacts.FirstOrDefaultAsync(c => + c.Content == contactMethod + ); + if (contact is not null) + { + contact.VerifiedAt = SystemClock.Instance.GetCurrentInstant(); + db.Update(contact); + } + + account = await db.Accounts.FirstOrDefaultAsync(c => c.Id == spell.AccountId); + if (account is not null) + { + account.ActivatedAt = SystemClock.Instance.GetCurrentInstant(); + db.Update(account); + } + + var defaultGroup = await db.PermissionGroups.FirstOrDefaultAsync(g => g.Key == "default"); + if (defaultGroup is not null && account is not null) + { + db.PermissionGroupMembers.Add(new PermissionGroupMember + { + Actor = $"user:{account.Id}", + Group = defaultGroup + }); + } + + break; + case MagicSpellType.ContactVerification: + var verifyContactMethod = (spell.Meta["contact_method"] as JsonElement? ?? default).ToString(); + var verifyContact = await db.AccountContacts + .FirstOrDefaultAsync(c => c.Content == verifyContactMethod); + if (verifyContact is not null) + { + verifyContact.VerifiedAt = SystemClock.Instance.GetCurrentInstant(); + db.Update(verifyContact); + } + + break; + default: + throw new ArgumentOutOfRangeException(); + } + + db.Remove(spell); + await db.SaveChangesAsync(); + } + + public async Task ApplyPasswordReset(MagicSpell spell, string newPassword) + { + if (spell.Type != MagicSpellType.AuthPasswordReset) + throw new ArgumentException("This spell is not a password reset spell."); + + var passwordFactor = await db.AccountAuthFactors + .Include(f => f.Account) + .Where(f => f.Type == AccountAuthFactorType.Password && f.Account.Id == spell.AccountId) + .FirstOrDefaultAsync(); + if (passwordFactor is null) + { + var account = await db.Accounts.FirstOrDefaultAsync(c => c.Id == spell.AccountId); + if (account is null) throw new InvalidOperationException("Both account and auth factor was not found."); + passwordFactor = new AccountAuthFactor + { + Type = AccountAuthFactorType.Password, + Account = account, + Secret = newPassword + }.HashSecret(); + db.AccountAuthFactors.Add(passwordFactor); + } + else + { + passwordFactor.Secret = newPassword; + passwordFactor.HashSecret(); + db.Update(passwordFactor); + } + + await db.SaveChangesAsync(); + } + + private static string _GenerateRandomString(int length) + { + using var rng = RandomNumberGenerator.Create(); + var randomBytes = new byte[length]; + rng.GetBytes(randomBytes); + + var base64String = Convert.ToBase64String(randomBytes); + + return base64String.Substring(0, length); + } +} \ No newline at end of file diff --git a/DysonNetwork.Pass/Account/Notification.cs b/DysonNetwork.Pass/Account/Notification.cs new file mode 100644 index 0000000..f2e2c8e --- /dev/null +++ b/DysonNetwork.Pass/Account/Notification.cs @@ -0,0 +1,41 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Text.Json.Serialization; +using Microsoft.EntityFrameworkCore; +using NodaTime; + +namespace DysonNetwork.Pass.Account; + +public class Notification : ModelBase +{ + public Guid Id { get; set; } = Guid.NewGuid(); + [MaxLength(1024)] public string Topic { get; set; } = null!; + [MaxLength(1024)] public string? Title { get; set; } + [MaxLength(2048)] public string? Subtitle { get; set; } + [MaxLength(4096)] public string? Content { get; set; } + [Column(TypeName = "jsonb")] public Dictionary? Meta { get; set; } + public int Priority { get; set; } = 10; + public Instant? ViewedAt { get; set; } + + public Guid AccountId { get; set; } + [JsonIgnore] public Account Account { get; set; } = null!; +} + +public enum NotificationPushProvider +{ + Apple, + Google +} + +[Index(nameof(DeviceToken), nameof(DeviceId), nameof(AccountId), IsUnique = true)] +public class NotificationPushSubscription : ModelBase +{ + public Guid Id { get; set; } = Guid.NewGuid(); + [MaxLength(4096)] public string DeviceId { get; set; } = null!; + [MaxLength(4096)] public string DeviceToken { get; set; } = null!; + public NotificationPushProvider Provider { get; set; } + public Instant? LastUsedAt { get; set; } + + public Guid AccountId { get; set; } + [JsonIgnore] public Account Account { get; set; } = null!; +} \ No newline at end of file diff --git a/DysonNetwork.Pass/Account/NotificationController.cs b/DysonNetwork.Pass/Account/NotificationController.cs new file mode 100644 index 0000000..8ad4681 --- /dev/null +++ b/DysonNetwork.Pass/Account/NotificationController.cs @@ -0,0 +1,166 @@ +using System.ComponentModel.DataAnnotations; +using DysonNetwork.Pass; +using DysonNetwork.Pass.Permission; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using NodaTime; + +namespace DysonNetwork.Pass.Account; + +[ApiController] +[Route("/api/notifications")] +public class NotificationController(AppDatabase db, NotificationService nty) : ControllerBase +{ + [HttpGet("count")] + [Authorize] + public async Task> CountUnreadNotifications() + { + HttpContext.Items.TryGetValue("CurrentUser", out var currentUserValue); + if (currentUserValue is not Account currentUser) return Unauthorized(); + + var count = await db.Notifications + .Where(s => s.AccountId == currentUser.Id && s.ViewedAt == null) + .CountAsync(); + return Ok(count); + } + + [HttpGet] + [Authorize] + public async Task>> ListNotifications( + [FromQuery] int offset = 0, + // The page size set to 5 is to avoid the client pulled the notification + // but didn't render it in the screen-viewable region. + [FromQuery] int take = 5 + ) + { + HttpContext.Items.TryGetValue("CurrentUser", out var currentUserValue); + if (currentUserValue is not Account currentUser) return Unauthorized(); + + var totalCount = await db.Notifications + .Where(s => s.AccountId == currentUser.Id) + .CountAsync(); + var notifications = await db.Notifications + .Where(s => s.AccountId == currentUser.Id) + .OrderByDescending(e => e.CreatedAt) + .Skip(offset) + .Take(take) + .ToListAsync(); + + Response.Headers["X-Total"] = totalCount.ToString(); + await nty.MarkNotificationsViewed(notifications); + + return Ok(notifications); + } + + public class PushNotificationSubscribeRequest + { + [MaxLength(4096)] public string DeviceToken { get; set; } = null!; + public NotificationPushProvider Provider { get; set; } + } + + [HttpPut("subscription")] + [Authorize] + public async Task> SubscribeToPushNotification( + [FromBody] PushNotificationSubscribeRequest request + ) + { + HttpContext.Items.TryGetValue("CurrentSession", out var currentSessionValue); + HttpContext.Items.TryGetValue("CurrentUser", out var currentUserValue); + var currentUser = currentUserValue as Account; + if (currentUser == null) return Unauthorized(); + var currentSession = currentSessionValue as Session; + if (currentSession == null) return Unauthorized(); + + var result = + await nty.SubscribePushNotification(currentUser, request.Provider, currentSession.Challenge.DeviceId!, + request.DeviceToken); + + return Ok(result); + } + + [HttpDelete("subscription")] + [Authorize] + public async Task> UnsubscribeFromPushNotification() + { + HttpContext.Items.TryGetValue("CurrentSession", out var currentSessionValue); + HttpContext.Items.TryGetValue("CurrentUser", out var currentUserValue); + var currentUser = currentUserValue as Account; + if (currentUser == null) return Unauthorized(); + var currentSession = currentSessionValue as Session; + if (currentSession == null) return Unauthorized(); + + var affectedRows = await db.NotificationPushSubscriptions + .Where(s => + s.AccountId == currentUser.Id && + s.DeviceId == currentSession.Challenge.DeviceId + ).ExecuteDeleteAsync(); + return Ok(affectedRows); + } + + public class NotificationRequest + { + [Required] [MaxLength(1024)] public string Topic { get; set; } = null!; + [Required] [MaxLength(1024)] public string Title { get; set; } = null!; + [MaxLength(2048)] public string? Subtitle { get; set; } + [Required] [MaxLength(4096)] public string Content { get; set; } = null!; + public Dictionary? Meta { get; set; } + public int Priority { get; set; } = 10; + } + + [HttpPost("broadcast")] + [Authorize] + [RequiredPermission("global", "notifications.broadcast")] + public async Task BroadcastNotification( + [FromBody] NotificationRequest request, + [FromQuery] bool save = false + ) + { + await nty.BroadcastNotification( + new Notification + { + CreatedAt = SystemClock.Instance.GetCurrentInstant(), + UpdatedAt = SystemClock.Instance.GetCurrentInstant(), + Topic = request.Topic, + Title = request.Title, + Subtitle = request.Subtitle, + Content = request.Content, + Meta = request.Meta, + Priority = request.Priority, + }, + save + ); + return Ok(); + } + + public class NotificationWithAimRequest : NotificationRequest + { + [Required] public List AccountId { get; set; } = null!; + } + + [HttpPost("send")] + [Authorize] + [RequiredPermission("global", "notifications.send")] + public async Task SendNotification( + [FromBody] NotificationWithAimRequest request, + [FromQuery] bool save = false + ) + { + var accounts = await db.Accounts.Where(a => request.AccountId.Contains(a.Id)).ToListAsync(); + await nty.SendNotificationBatch( + new Notification + { + CreatedAt = SystemClock.Instance.GetCurrentInstant(), + UpdatedAt = SystemClock.Instance.GetCurrentInstant(), + Topic = request.Topic, + Title = request.Title, + Subtitle = request.Subtitle, + Content = request.Content, + Meta = request.Meta, + }, + accounts, + save + ); + return Ok(); + } +} \ No newline at end of file diff --git a/DysonNetwork.Pass/Account/NotificationService.cs b/DysonNetwork.Pass/Account/NotificationService.cs new file mode 100644 index 0000000..7fd0099 --- /dev/null +++ b/DysonNetwork.Pass/Account/NotificationService.cs @@ -0,0 +1,308 @@ +using System.Text; +using System.Text.Json; +using DysonNetwork.Pass; +using EFCore.BulkExtensions; +using Microsoft.EntityFrameworkCore; +using NodaTime; + +namespace DysonNetwork.Pass.Account; + +public class NotificationService( + AppDatabase db, + WebSocketService ws, + IHttpClientFactory httpFactory, + IConfiguration config) +{ + private readonly string _notifyTopic = config["Notifications:Topic"]!; + private readonly Uri _notifyEndpoint = new(config["Notifications:Endpoint"]!); + + public async Task UnsubscribePushNotifications(string deviceId) + { + await db.NotificationPushSubscriptions + .Where(s => s.DeviceId == deviceId) + .ExecuteDeleteAsync(); + } + + public async Task SubscribePushNotification( + Account account, + NotificationPushProvider provider, + string deviceId, + string deviceToken + ) + { + var now = SystemClock.Instance.GetCurrentInstant(); + + // First check if a matching subscription exists + var existingSubscription = await db.NotificationPushSubscriptions + .Where(s => s.AccountId == account.Id) + .Where(s => s.DeviceId == deviceId || s.DeviceToken == deviceToken) + .FirstOrDefaultAsync(); + + if (existingSubscription is not null) + { + // Update the existing subscription directly in the database + await db.NotificationPushSubscriptions + .Where(s => s.Id == existingSubscription.Id) + .ExecuteUpdateAsync(setters => setters + .SetProperty(s => s.DeviceId, deviceId) + .SetProperty(s => s.DeviceToken, deviceToken) + .SetProperty(s => s.UpdatedAt, now)); + + // Return the updated subscription + existingSubscription.DeviceId = deviceId; + existingSubscription.DeviceToken = deviceToken; + existingSubscription.UpdatedAt = now; + return existingSubscription; + } + + var subscription = new NotificationPushSubscription + { + DeviceId = deviceId, + DeviceToken = deviceToken, + Provider = provider, + AccountId = account.Id, + }; + + db.NotificationPushSubscriptions.Add(subscription); + await db.SaveChangesAsync(); + + return subscription; + } + + public async Task SendNotification( + Account account, + string topic, + string? title = null, + string? subtitle = null, + string? content = null, + Dictionary? meta = null, + string? actionUri = null, + bool isSilent = false, + bool save = true + ) + { + if (title is null && subtitle is null && content is null) + throw new ArgumentException("Unable to send notification that completely empty."); + + meta ??= new Dictionary(); + if (actionUri is not null) meta["action_uri"] = actionUri; + + var notification = new Notification + { + Topic = topic, + Title = title, + Subtitle = subtitle, + Content = content, + Meta = meta, + AccountId = account.Id, + }; + + if (save) + { + db.Add(notification); + await db.SaveChangesAsync(); + } + + if (!isSilent) _ = DeliveryNotification(notification); + + return notification; + } + + public async Task DeliveryNotification(Notification notification) + { + ws.SendPacketToAccount(notification.AccountId, new WebSocketPacket + { + Type = "notifications.new", + Data = notification + }); + + // Pushing the notification + var subscribers = await db.NotificationPushSubscriptions + .Where(s => s.AccountId == notification.AccountId) + .ToListAsync(); + + await _PushNotification(notification, subscribers); + } + + public async Task MarkNotificationsViewed(ICollection notifications) + { + var now = SystemClock.Instance.GetCurrentInstant(); + var id = notifications.Where(n => n.ViewedAt == null).Select(n => n.Id).ToList(); + if (id.Count == 0) return; + + await db.Notifications + .Where(n => id.Contains(n.Id)) + .ExecuteUpdateAsync(s => s.SetProperty(n => n.ViewedAt, now) + ); + } + + public async Task BroadcastNotification(Notification notification, bool save = false) + { + var accounts = await db.Accounts.ToListAsync(); + + if (save) + { + var notifications = accounts.Select(x => + { + var newNotification = new Notification + { + Topic = notification.Topic, + Title = notification.Title, + Subtitle = notification.Subtitle, + Content = notification.Content, + Meta = notification.Meta, + Priority = notification.Priority, + Account = x, + AccountId = x.Id + }; + return newNotification; + }).ToList(); + await db.BulkInsertAsync(notifications); + } + + foreach (var account in accounts) + { + notification.Account = account; + notification.AccountId = account.Id; + ws.SendPacketToAccount(account.Id, new WebSocketPacket + { + Type = "notifications.new", + Data = notification + }); + } + + var subscribers = await db.NotificationPushSubscriptions + .ToListAsync(); + await _PushNotification(notification, subscribers); + } + + public async Task SendNotificationBatch(Notification notification, List accounts, bool save = false) + { + if (save) + { + var notifications = accounts.Select(x => + { + var newNotification = new Notification + { + Topic = notification.Topic, + Title = notification.Title, + Subtitle = notification.Subtitle, + Content = notification.Content, + Meta = notification.Meta, + Priority = notification.Priority, + Account = x, + AccountId = x.Id + }; + return newNotification; + }).ToList(); + await db.BulkInsertAsync(notifications); + } + + foreach (var account in accounts) + { + notification.Account = account; + notification.AccountId = account.Id; + ws.SendPacketToAccount(account.Id, new WebSocketPacket + { + Type = "notifications.new", + Data = notification + }); + } + + var accountsId = accounts.Select(x => x.Id).ToList(); + var subscribers = await db.NotificationPushSubscriptions + .Where(s => accountsId.Contains(s.AccountId)) + .ToListAsync(); + await _PushNotification(notification, subscribers); + } + + private List> _BuildNotificationPayload(Notification notification, + IEnumerable subscriptions) + { + var subDict = subscriptions + .GroupBy(x => x.Provider) + .ToDictionary(x => x.Key, x => x.ToList()); + + var notifications = subDict.Select(value => + { + var platformCode = value.Key switch + { + NotificationPushProvider.Apple => 1, + NotificationPushProvider.Google => 2, + _ => throw new InvalidOperationException($"Unknown push provider: {value.Key}") + }; + + var tokens = value.Value.Select(x => x.DeviceToken).ToList(); + return _BuildNotificationPayload(notification, platformCode, tokens); + }).ToList(); + + return notifications.ToList(); + } + + private Dictionary _BuildNotificationPayload(Notification notification, int platformCode, + IEnumerable deviceTokens) + { + var alertDict = new Dictionary(); + var dict = new Dictionary + { + ["notif_id"] = notification.Id.ToString(), + ["apns_id"] = notification.Id.ToString(), + ["topic"] = _notifyTopic, + ["tokens"] = deviceTokens, + ["data"] = new Dictionary + { + ["type"] = notification.Topic, + ["meta"] = notification.Meta ?? new Dictionary(), + }, + ["mutable_content"] = true, + ["priority"] = notification.Priority >= 5 ? "high" : "normal", + }; + + if (!string.IsNullOrWhiteSpace(notification.Title)) + { + dict["title"] = notification.Title; + alertDict["title"] = notification.Title; + } + + if (!string.IsNullOrWhiteSpace(notification.Content)) + { + dict["message"] = notification.Content; + alertDict["body"] = notification.Content; + } + + if (!string.IsNullOrWhiteSpace(notification.Subtitle)) + { + dict["message"] = $"{notification.Subtitle}\n{dict["message"]}"; + alertDict["subtitle"] = notification.Subtitle; + } + + if (notification.Priority >= 5) + dict["name"] = "default"; + + dict["platform"] = platformCode; + dict["alert"] = alertDict; + + return dict; + } + + private async Task _PushNotification(Notification notification, + IEnumerable subscriptions) + { + var subList = subscriptions.ToList(); + if (subList.Count == 0) return; + + var requestDict = new Dictionary + { + ["notifications"] = _BuildNotificationPayload(notification, subList) + }; + + var client = httpFactory.CreateClient(); + client.BaseAddress = _notifyEndpoint; + var request = await client.PostAsync("/push", new StringContent( + JsonSerializer.Serialize(requestDict), + Encoding.UTF8, + "application/json" + )); + request.EnsureSuccessStatusCode(); + } +} \ No newline at end of file diff --git a/DysonNetwork.Pass/Account/Relationship.cs b/DysonNetwork.Pass/Account/Relationship.cs new file mode 100644 index 0000000..7b4aedd --- /dev/null +++ b/DysonNetwork.Pass/Account/Relationship.cs @@ -0,0 +1,22 @@ +using NodaTime; + +namespace DysonNetwork.Pass.Account; + +public enum RelationshipStatus : short +{ + Friends = 100, + Pending = 0, + Blocked = -100 +} + +public class Relationship : ModelBase +{ + public Guid AccountId { get; set; } + public Account Account { get; set; } = null!; + public Guid RelatedId { get; set; } + public Account Related { get; set; } = null!; + + public Instant? ExpiredAt { get; set; } + + public RelationshipStatus Status { get; set; } = RelationshipStatus.Pending; +} \ No newline at end of file diff --git a/DysonNetwork.Pass/Account/RelationshipController.cs b/DysonNetwork.Pass/Account/RelationshipController.cs new file mode 100644 index 0000000..4f0729d --- /dev/null +++ b/DysonNetwork.Pass/Account/RelationshipController.cs @@ -0,0 +1,253 @@ +using System.ComponentModel.DataAnnotations; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using NodaTime; + +namespace DysonNetwork.Pass.Account; + +[ApiController] +[Route("/api/relationships")] +public class RelationshipController(AppDatabase db, RelationshipService rels) : ControllerBase +{ + [HttpGet] + [Authorize] + public async Task>> ListRelationships([FromQuery] int offset = 0, + [FromQuery] int take = 20) + { + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); + var userId = currentUser.Id; + + var query = db.AccountRelationships.AsQueryable() + .Where(r => r.RelatedId == userId); + var totalCount = await query.CountAsync(); + var relationships = await query + .Include(r => r.Related) + .Include(r => r.Related.Profile) + .Include(r => r.Account) + .Include(r => r.Account.Profile) + .Skip(offset) + .Take(take) + .ToListAsync(); + + var statuses = await db.AccountRelationships + .Where(r => r.AccountId == userId) + .ToDictionaryAsync(r => r.RelatedId); + foreach (var relationship in relationships) + if (statuses.TryGetValue(relationship.RelatedId, out var status)) + relationship.Status = status.Status; + + Response.Headers["X-Total"] = totalCount.ToString(); + + return relationships; + } + + [HttpGet("requests")] + [Authorize] + public async Task>> ListSentRequests() + { + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); + + var relationships = await db.AccountRelationships + .Where(r => r.AccountId == currentUser.Id && r.Status == RelationshipStatus.Pending) + .Include(r => r.Related) + .Include(r => r.Related.Profile) + .Include(r => r.Account) + .Include(r => r.Account.Profile) + .ToListAsync(); + + return relationships; + } + + public class RelationshipRequest + { + [Required] public RelationshipStatus Status { get; set; } + } + + [HttpPost("{userId:guid}")] + [Authorize] + public async Task> CreateRelationship(Guid userId, + [FromBody] RelationshipRequest request) + { + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); + + var relatedUser = await db.Accounts.FindAsync(userId); + if (relatedUser is null) return NotFound("Account was not found."); + + try + { + var relationship = await rels.CreateRelationship( + currentUser, relatedUser, request.Status + ); + return relationship; + } + catch (InvalidOperationException err) + { + return BadRequest(err.Message); + } + } + + [HttpPatch("{userId:guid}")] + [Authorize] + public async Task> UpdateRelationship(Guid userId, + [FromBody] RelationshipRequest request) + { + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); + + try + { + var relationship = await rels.UpdateRelationship(currentUser.Id, userId, request.Status); + return relationship; + } + catch (ArgumentException err) + { + return NotFound(err.Message); + } + catch (InvalidOperationException err) + { + return BadRequest(err.Message); + } + } + + [HttpGet("{userId:guid}")] + [Authorize] + public async Task> GetRelationship(Guid userId) + { + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); + + var now = Instant.FromDateTimeUtc(DateTime.UtcNow); + var queries = db.AccountRelationships.AsQueryable() + .Where(r => r.AccountId == currentUser.Id && r.RelatedId == userId) + .Where(r => r.ExpiredAt == null || r.ExpiredAt > now); + var relationship = await queries + .Include(r => r.Related) + .Include(r => r.Related.Profile) + .FirstOrDefaultAsync(); + if (relationship is null) return NotFound(); + + relationship.Account = currentUser; + return Ok(relationship); + } + + [HttpPost("{userId:guid}/friends")] + [Authorize] + public async Task> SendFriendRequest(Guid userId) + { + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); + + var relatedUser = await db.Accounts.FindAsync(userId); + if (relatedUser is null) return NotFound("Account was not found."); + + var existing = await db.AccountRelationships.FirstOrDefaultAsync(r => + (r.AccountId == currentUser.Id && r.RelatedId == userId) || + (r.AccountId == userId && r.RelatedId == currentUser.Id)); + if (existing != null) return BadRequest("Relationship already exists."); + + try + { + var relationship = await rels.SendFriendRequest(currentUser, relatedUser); + return relationship; + } + catch (InvalidOperationException err) + { + return BadRequest(err.Message); + } + } + + [HttpDelete("{userId:guid}/friends")] + [Authorize] + public async Task DeleteFriendRequest(Guid userId) + { + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); + + try + { + await rels.DeleteFriendRequest(currentUser.Id, userId); + return NoContent(); + } + catch (ArgumentException err) + { + return NotFound(err.Message); + } + } + + [HttpPost("{userId:guid}/friends/accept")] + [Authorize] + public async Task> AcceptFriendRequest(Guid userId) + { + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); + + var relationship = await rels.GetRelationship(userId, currentUser.Id, RelationshipStatus.Pending); + if (relationship is null) return NotFound("Friend request was not found."); + + try + { + relationship = await rels.AcceptFriendRelationship(relationship); + return relationship; + } + catch (InvalidOperationException err) + { + return BadRequest(err.Message); + } + } + + [HttpPost("{userId:guid}/friends/decline")] + [Authorize] + public async Task> DeclineFriendRequest(Guid userId) + { + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); + + var relationship = await rels.GetRelationship(userId, currentUser.Id, RelationshipStatus.Pending); + if (relationship is null) return NotFound("Friend request was not found."); + + try + { + relationship = await rels.AcceptFriendRelationship(relationship, status: RelationshipStatus.Blocked); + return relationship; + } + catch (InvalidOperationException err) + { + return BadRequest(err.Message); + } + } + + [HttpPost("{userId:guid}/block")] + [Authorize] + public async Task> BlockUser(Guid userId) + { + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); + + var relatedUser = await db.Accounts.FindAsync(userId); + if (relatedUser is null) return NotFound("Account was not found."); + + try + { + var relationship = await rels.BlockAccount(currentUser, relatedUser); + return relationship; + } + catch (InvalidOperationException err) + { + return BadRequest(err.Message); + } + } + + [HttpDelete("{userId:guid}/block")] + [Authorize] + public async Task> UnblockUser(Guid userId) + { + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); + + var relatedUser = await db.Accounts.FindAsync(userId); + if (relatedUser is null) return NotFound("Account was not found."); + + try + { + var relationship = await rels.UnblockAccount(currentUser, relatedUser); + return relationship; + } + catch (InvalidOperationException err) + { + return BadRequest(err.Message); + } + } +} \ No newline at end of file diff --git a/DysonNetwork.Pass/Account/RelationshipService.cs b/DysonNetwork.Pass/Account/RelationshipService.cs new file mode 100644 index 0000000..20be861 --- /dev/null +++ b/DysonNetwork.Pass/Account/RelationshipService.cs @@ -0,0 +1,207 @@ +using DysonNetwork.Shared.Cache; +using Microsoft.EntityFrameworkCore; +using NodaTime; + +namespace DysonNetwork.Pass.Account; + +public class RelationshipService(AppDatabase db, ICacheService cache) +{ + private const string UserFriendsCacheKeyPrefix = "accounts:friends:"; + private const string UserBlockedCacheKeyPrefix = "accounts:blocked:"; + + public async Task HasExistingRelationship(Guid accountId, Guid relatedId) + { + var count = await db.AccountRelationships + .Where(r => (r.AccountId == accountId && r.RelatedId == relatedId) || + (r.AccountId == relatedId && r.AccountId == accountId)) + .CountAsync(); + return count > 0; + } + + public async Task GetRelationship( + Guid accountId, + Guid relatedId, + RelationshipStatus? status = null, + bool ignoreExpired = false + ) + { + var now = Instant.FromDateTimeUtc(DateTime.UtcNow); + var queries = db.AccountRelationships.AsQueryable() + .Where(r => r.AccountId == accountId && r.RelatedId == relatedId); + if (!ignoreExpired) queries = queries.Where(r => r.ExpiredAt == null || r.ExpiredAt > now); + if (status is not null) queries = queries.Where(r => r.Status == status); + var relationship = await queries.FirstOrDefaultAsync(); + return relationship; + } + + public async Task CreateRelationship(Account sender, Account target, RelationshipStatus status) + { + if (status == RelationshipStatus.Pending) + throw new InvalidOperationException( + "Cannot create relationship with pending status, use SendFriendRequest instead."); + if (await HasExistingRelationship(sender.Id, target.Id)) + throw new InvalidOperationException("Found existing relationship between you and target user."); + + var relationship = new Relationship + { + AccountId = sender.Id, + RelatedId = target.Id, + Status = status + }; + + db.AccountRelationships.Add(relationship); + await db.SaveChangesAsync(); + + await PurgeRelationshipCache(sender.Id, target.Id); + + return relationship; + } + + public async Task BlockAccount(Account sender, Account target) + { + if (await HasExistingRelationship(sender.Id, target.Id)) + return await UpdateRelationship(sender.Id, target.Id, RelationshipStatus.Blocked); + return await CreateRelationship(sender, target, RelationshipStatus.Blocked); + } + + public async Task UnblockAccount(Account sender, Account target) + { + var relationship = await GetRelationship(sender.Id, target.Id, RelationshipStatus.Blocked); + if (relationship is null) throw new ArgumentException("There is no relationship between you and the user."); + db.Remove(relationship); + await db.SaveChangesAsync(); + + await PurgeRelationshipCache(sender.Id, target.Id); + + return relationship; + } + + public async Task SendFriendRequest(Account sender, Account target) + { + if (await HasExistingRelationship(sender.Id, target.Id)) + throw new InvalidOperationException("Found existing relationship between you and target user."); + + var relationship = new Relationship + { + AccountId = sender.Id, + RelatedId = target.Id, + Status = RelationshipStatus.Pending, + ExpiredAt = Instant.FromDateTimeUtc(DateTime.UtcNow.AddDays(7)) + }; + + db.AccountRelationships.Add(relationship); + await db.SaveChangesAsync(); + + return relationship; + } + + public async Task DeleteFriendRequest(Guid accountId, Guid relatedId) + { + var relationship = await GetRelationship(accountId, relatedId, RelationshipStatus.Pending); + if (relationship is null) throw new ArgumentException("Friend request was not found."); + + await db.AccountRelationships + .Where(r => r.AccountId == accountId && r.RelatedId == relatedId && r.Status == RelationshipStatus.Pending) + .ExecuteDeleteAsync(); + + await PurgeRelationshipCache(relationship.AccountId, relationship.RelatedId); + } + + public async Task AcceptFriendRelationship( + Relationship relationship, + RelationshipStatus status = RelationshipStatus.Friends + ) + { + if (relationship.Status != RelationshipStatus.Pending) + throw new ArgumentException("Cannot accept friend request that not in pending status."); + if (status == RelationshipStatus.Pending) + throw new ArgumentException("Cannot accept friend request by setting the new status to pending."); + + // Whatever the receiver decides to apply which status to the relationship, + // the sender should always see the user as a friend since the sender ask for it + relationship.Status = RelationshipStatus.Friends; + relationship.ExpiredAt = null; + db.Update(relationship); + + var relationshipBackward = new Relationship + { + AccountId = relationship.RelatedId, + RelatedId = relationship.AccountId, + Status = status + }; + db.AccountRelationships.Add(relationshipBackward); + + await db.SaveChangesAsync(); + + await PurgeRelationshipCache(relationship.AccountId, relationship.RelatedId); + + return relationshipBackward; + } + + public async Task UpdateRelationship(Guid accountId, Guid relatedId, RelationshipStatus status) + { + var relationship = await GetRelationship(accountId, relatedId); + if (relationship is null) throw new ArgumentException("There is no relationship between you and the user."); + if (relationship.Status == status) return relationship; + relationship.Status = status; + db.Update(relationship); + await db.SaveChangesAsync(); + + await PurgeRelationshipCache(accountId, relatedId); + + return relationship; + } + + public async Task> ListAccountFriends(Account account) + { + var cacheKey = $"{UserFriendsCacheKeyPrefix}{account.Id}"; + var friends = await cache.GetAsync>(cacheKey); + + if (friends == null) + { + friends = await db.AccountRelationships + .Where(r => r.RelatedId == account.Id) + .Where(r => r.Status == RelationshipStatus.Friends) + .Select(r => r.AccountId) + .ToListAsync(); + + await cache.SetAsync(cacheKey, friends, TimeSpan.FromHours(1)); + } + + return friends ?? []; + } + + public async Task> ListAccountBlocked(Account account) + { + var cacheKey = $"{UserBlockedCacheKeyPrefix}{account.Id}"; + var blocked = await cache.GetAsync>(cacheKey); + + if (blocked == null) + { + blocked = await db.AccountRelationships + .Where(r => r.RelatedId == account.Id) + .Where(r => r.Status == RelationshipStatus.Blocked) + .Select(r => r.AccountId) + .ToListAsync(); + + await cache.SetAsync(cacheKey, blocked, TimeSpan.FromHours(1)); + } + + return blocked ?? []; + } + + public async Task HasRelationshipWithStatus(Guid accountId, Guid relatedId, + RelationshipStatus status = RelationshipStatus.Friends) + { + var relationship = await GetRelationship(accountId, relatedId, status); + return relationship is not null; + } + + private async Task PurgeRelationshipCache(Guid accountId, Guid relatedId) + { + await cache.RemoveAsync($"{UserFriendsCacheKeyPrefix}{accountId}"); + await cache.RemoveAsync($"{UserFriendsCacheKeyPrefix}{relatedId}"); + await cache.RemoveAsync($"{UserBlockedCacheKeyPrefix}{accountId}"); + await cache.RemoveAsync($"{UserBlockedCacheKeyPrefix}{relatedId}"); + } +} \ No newline at end of file diff --git a/DysonNetwork.Pass/Account/VerificationMark.cs b/DysonNetwork.Pass/Account/VerificationMark.cs new file mode 100644 index 0000000..fc6a419 --- /dev/null +++ b/DysonNetwork.Pass/Account/VerificationMark.cs @@ -0,0 +1,25 @@ +using System.ComponentModel.DataAnnotations; + +namespace DysonNetwork.Pass.Account; + +/// +/// The verification info of a resource +/// stands, for it is really an individual or organization or a company in the real world. +/// Besides, it can also be use for mark parody or fake. +/// +public class VerificationMark +{ + public VerificationMarkType Type { get; set; } + [MaxLength(1024)] public string? Title { get; set; } + [MaxLength(8192)] public string? Description { get; set; } + [MaxLength(1024)] public string? VerifiedBy { get; set; } +} + +public enum VerificationMarkType +{ + Official, + Individual, + Organization, + Government, + Creator +} \ No newline at end of file diff --git a/DysonNetwork.Pass/AppDatabase.cs b/DysonNetwork.Pass/AppDatabase.cs new file mode 100644 index 0000000..424af99 --- /dev/null +++ b/DysonNetwork.Pass/AppDatabase.cs @@ -0,0 +1,275 @@ +using System.Linq.Expressions; +using System.Reflection; +using DysonNetwork.Pass.Account; +using DysonNetwork.Pass.Auth; +using DysonNetwork.Pass.Permission; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Design; +using Microsoft.EntityFrameworkCore.Query; +using NodaTime; +using Quartz; + +namespace DysonNetwork.Pass; + +public interface IIdentifiedResource +{ + public string ResourceIdentifier { get; } +} + +public abstract class ModelBase +{ + public Instant CreatedAt { get; set; } + public Instant UpdatedAt { get; set; } + public Instant? DeletedAt { get; set; } +} + +public class AppDatabase( + DbContextOptions options, + IConfiguration configuration +) : DbContext(options) +{ + public DbSet PermissionNodes { get; set; } + public DbSet PermissionGroups { get; set; } + public DbSet PermissionGroupMembers { get; set; } + + public DbSet MagicSpells { get; set; } + public DbSet Accounts { get; set; } + public DbSet AccountConnections { get; set; } + public DbSet AccountProfiles { get; set; } + public DbSet AccountContacts { get; set; } + public DbSet AccountAuthFactors { get; set; } + public DbSet AccountRelationships { get; set; } + public DbSet AccountStatuses { get; set; } + public DbSet AccountCheckInResults { get; set; } + public DbSet Notifications { get; set; } + public DbSet NotificationPushSubscriptions { get; set; } + public DbSet Badges { get; set; } + public DbSet ActionLogs { get; set; } + public DbSet AbuseReports { get; set; } + + public DbSet AuthSessions { get; set; } + public DbSet AuthChallenges { get; set; } + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + optionsBuilder.UseNpgsql( + configuration.GetConnectionString("App"), + opt => opt + .ConfigureDataSource(optSource => optSource.EnableDynamicJson()) + .UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery) + .UseNetTopologySuite() + .UseNodaTime() + ).UseSnakeCaseNamingConvention(); + + optionsBuilder.UseAsyncSeeding(async (context, _, cancellationToken) => + { + var defaultPermissionGroup = await context.Set() + .FirstOrDefaultAsync(g => g.Key == "default", cancellationToken); + if (defaultPermissionGroup is null) + { + context.Set().Add(new PermissionGroup + { + Key = "default", + Nodes = new List + { + "posts.create", + "posts.react", + "publishers.create", + "files.create", + "chat.create", + "chat.messages.create", + "chat.realtime.create", + "accounts.statuses.create", + "accounts.statuses.update", + "stickers.packs.create", + "stickers.create" + }.Select(permission => + PermissionService.NewPermissionNode("group:default", "global", permission, true)) + .ToList() + }); + await context.SaveChangesAsync(cancellationToken); + } + }); + + optionsBuilder.UseSeeding((context, _) => {}); + + base.OnConfiguring(optionsBuilder); + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + modelBuilder.Entity() + .HasKey(pg => new { pg.GroupId, pg.Actor }); + modelBuilder.Entity() + .HasOne(pg => pg.Group) + .WithMany(g => g.Members) + .HasForeignKey(pg => pg.GroupId) + .OnDelete(DeleteBehavior.Cascade); + + modelBuilder.Entity() + .HasKey(r => new { FromAccountId = r.AccountId, ToAccountId = r.RelatedId }); + modelBuilder.Entity() + .HasOne(r => r.Account) + .WithMany(a => a.OutgoingRelationships) + .HasForeignKey(r => r.AccountId); + modelBuilder.Entity() + .HasOne(r => r.Related) + .WithMany(a => a.IncomingRelationships) + .HasForeignKey(r => r.RelatedId); + + // Automatically apply soft-delete filter to all entities inheriting BaseModel + foreach (var entityType in modelBuilder.Model.GetEntityTypes()) + { + if (!typeof(ModelBase).IsAssignableFrom(entityType.ClrType)) continue; + var method = typeof(AppDatabase) + .GetMethod(nameof(SetSoftDeleteFilter), + BindingFlags.NonPublic | BindingFlags.Static)! + .MakeGenericMethod(entityType.ClrType); + + method.Invoke(null, [modelBuilder]); + } + } + + private static void SetSoftDeleteFilter(ModelBuilder modelBuilder) + where TEntity : ModelBase + { + modelBuilder.Entity().HasQueryFilter(e => e.DeletedAt == null); + } + + public override async Task SaveChangesAsync(CancellationToken cancellationToken = default) + { + var now = SystemClock.Instance.GetCurrentInstant(); + + foreach (var entry in ChangeTracker.Entries()) + { + switch (entry.State) + { + case EntityState.Added: + entry.Entity.CreatedAt = now; + entry.Entity.UpdatedAt = now; + break; + case EntityState.Modified: + entry.Entity.UpdatedAt = now; + break; + case EntityState.Deleted: + entry.State = EntityState.Modified; + entry.Entity.DeletedAt = now; + break; + case EntityState.Detached: + case EntityState.Unchanged: + default: + break; + } + } + + return await base.SaveChangesAsync(cancellationToken); + } +} + +public class AppDatabaseRecyclingJob(AppDatabase db, ILogger logger) : IJob +{ + public async Task Execute(IJobExecutionContext context) + { + var now = SystemClock.Instance.GetCurrentInstant(); + + logger.LogInformation("Cleaning up expired records..."); + + // Expired relationships + var affectedRows = await db.AccountRelationships + .Where(x => x.ExpiredAt != null && x.ExpiredAt <= now) + .ExecuteDeleteAsync(); + logger.LogDebug("Removed {Count} records of expired relationships.", affectedRows); + // Expired permission group members + affectedRows = await db.PermissionGroupMembers + .Where(x => x.ExpiredAt != null && x.ExpiredAt <= now) + .ExecuteDeleteAsync(); + logger.LogDebug("Removed {Count} records of expired permission group members.", affectedRows); + + logger.LogInformation("Deleting soft-deleted records..."); + + var threshold = now - Duration.FromDays(7); + + var entityTypes = db.Model.GetEntityTypes() + .Where(t => typeof(ModelBase).IsAssignableFrom(t.ClrType) && t.ClrType != typeof(ModelBase)) + .Select(t => t.ClrType); + + foreach (var entityType in entityTypes) + { + var set = (IQueryable)db.GetType().GetMethod(nameof(DbContext.Set), Type.EmptyTypes)! + .MakeGenericMethod(entityType).Invoke(db, null)!; + var parameter = Expression.Parameter(entityType, "e"); + var property = Expression.Property(parameter, nameof(ModelBase.DeletedAt)); + var condition = Expression.LessThan(property, Expression.Constant(threshold, typeof(Instant?))); + var notNull = Expression.NotEqual(property, Expression.Constant(null, typeof(Instant?))); + var finalCondition = Expression.AndAlso(notNull, condition); + var lambda = Expression.Lambda(finalCondition, parameter); + + var queryable = set.Provider.CreateQuery( + Expression.Call( + typeof(Queryable), + "Where", + [entityType], + set.Expression, + Expression.Quote(lambda) + ) + ); + + var toListAsync = typeof(EntityFrameworkQueryableExtensions) + .GetMethod(nameof(EntityFrameworkQueryableExtensions.ToListAsync))! + .MakeGenericMethod(entityType); + + var items = await (dynamic)toListAsync.Invoke(null, [queryable, CancellationToken.None])!; + db.RemoveRange(items); + } + + await db.SaveChangesAsync(); + } +} + +public class AppDatabaseFactory : IDesignTimeDbContextFactory +{ + public AppDatabase CreateDbContext(string[] args) + { + var configuration = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json") + .Build(); + + var optionsBuilder = new DbContextOptionsBuilder(); + return new AppDatabase(optionsBuilder.Options, configuration); + } +} + +public static class OptionalQueryExtensions +{ + public static IQueryable If( + this IQueryable source, + bool condition, + Func, IQueryable> transform + ) + { + return condition ? transform(source) : source; + } + + public static IQueryable If( + this IIncludableQueryable source, + bool condition, + Func, IQueryable> transform + ) + where T : class + { + return condition ? transform(source) : source; + } + + public static IQueryable If( + this IIncludableQueryable> source, + bool condition, + Func>, IQueryable> transform + ) + where T : class + { + return condition ? transform(source) : source; + } +} \ No newline at end of file diff --git a/DysonNetwork.Pass/Auth/Auth.cs b/DysonNetwork.Pass/Auth/Auth.cs new file mode 100644 index 0000000..fd8632a --- /dev/null +++ b/DysonNetwork.Pass/Auth/Auth.cs @@ -0,0 +1,273 @@ +using System.Security.Claims; +using System.Security.Cryptography; +using System.Text.Encodings.Web; +using DysonNetwork.Pass.Account; +using Microsoft.AspNetCore.Authentication; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Options; +using DysonNetwork.Pass.Auth.OidcProvider.Services; +using DysonNetwork.Pass.Handlers; +using DysonNetwork.Shared.Cache; +using SystemClock = NodaTime.SystemClock; + +namespace DysonNetwork.Pass.Auth; + +public static class AuthConstants +{ + public const string SchemeName = "DysonToken"; + public const string TokenQueryParamName = "tk"; + public const string CookieTokenName = "AuthToken"; +} + +public enum TokenType +{ + AuthKey, + ApiKey, + OidcKey, + Unknown +} + +public class TokenInfo +{ + public string Token { get; set; } = string.Empty; + public TokenType Type { get; set; } = TokenType.Unknown; +} + +public class DysonTokenAuthOptions : AuthenticationSchemeOptions; + +public class DysonTokenAuthHandler( + IOptionsMonitor options, + IConfiguration configuration, + ILoggerFactory logger, + UrlEncoder encoder, + AppDatabase database, + OidcProviderService oidc, + ICacheService cache, + FlushBufferService fbs +) + : AuthenticationHandler(options, logger, encoder) +{ + public const string AuthCachePrefix = "auth:"; + + protected override async Task HandleAuthenticateAsync() + { + var tokenInfo = _ExtractToken(Request); + + if (tokenInfo == null || string.IsNullOrEmpty(tokenInfo.Token)) + return AuthenticateResult.Fail("No token was provided."); + + try + { + var now = SystemClock.Instance.GetCurrentInstant(); + + // Validate token and extract session ID + if (!ValidateToken(tokenInfo.Token, out var sessionId)) + return AuthenticateResult.Fail("Invalid token."); + + // Try to get session from cache first + var session = await cache.GetAsync($"{AuthCachePrefix}{sessionId}"); + + // If not in cache, load from database + if (session is null) + { + session = await database.AuthSessions + .Where(e => e.Id == sessionId) + .Include(e => e.Challenge) + .Include(e => e.Account) + .ThenInclude(e => e.Profile) + .FirstOrDefaultAsync(); + + if (session is not null) + { + // Store in cache for future requests + await cache.SetWithGroupsAsync( + $"auth:{sessionId}", + session, + [$"{AccountService.AccountCachePrefix}{session.Account.Id}"], + TimeSpan.FromHours(1) + ); + } + } + + // Check if the session exists + if (session == null) + return AuthenticateResult.Fail("Session not found."); + + // Check if the session is expired + if (session.ExpiredAt.HasValue && session.ExpiredAt.Value < now) + return AuthenticateResult.Fail("Session expired."); + + // Store user and session in the HttpContext.Items for easy access in controllers + Context.Items["CurrentUser"] = session.Account; + Context.Items["CurrentSession"] = session; + Context.Items["CurrentTokenType"] = tokenInfo.Type.ToString(); + + // Create claims from the session + var claims = new List + { + new("user_id", session.Account.Id.ToString()), + new("session_id", session.Id.ToString()), + new("token_type", tokenInfo.Type.ToString()) + }; + + // Add scopes as claims + session.Challenge.Scopes.ForEach(scope => claims.Add(new Claim("scope", scope))); + + // Add superuser claim if applicable + if (session.Account.IsSuperuser) + claims.Add(new Claim("is_superuser", "1")); + + // Create the identity and principal + var identity = new ClaimsIdentity(claims, AuthConstants.SchemeName); + var principal = new ClaimsPrincipal(identity); + + var ticket = new AuthenticationTicket(principal, AuthConstants.SchemeName); + + var lastInfo = new LastActiveInfo + { + Account = session.Account, + Session = session, + SeenAt = SystemClock.Instance.GetCurrentInstant(), + }; + fbs.Enqueue(lastInfo); + + return AuthenticateResult.Success(ticket); + } + catch (Exception ex) + { + return AuthenticateResult.Fail($"Authentication failed: {ex.Message}"); + } + } + + private bool ValidateToken(string token, out Guid sessionId) + { + sessionId = Guid.Empty; + + try + { + var parts = token.Split('.'); + + switch (parts.Length) + { + // Handle JWT tokens (3 parts) + case 3: + { + var (isValid, jwtResult) = oidc.ValidateToken(token); + if (!isValid) return false; + var jti = jwtResult?.Claims.FirstOrDefault(c => c.Type == "jti")?.Value; + if (jti is null) return false; + + return Guid.TryParse(jti, out sessionId); + } + // Handle compact tokens (2 parts) + case 2: + // Original compact token validation logic + try + { + // Decode the payload + var payloadBytes = Base64UrlDecode(parts[0]); + + // Extract session ID + sessionId = new Guid(payloadBytes); + + // Load public key for verification + var publicKeyPem = File.ReadAllText(configuration["AuthToken:PublicKeyPath"]!); + using var rsa = RSA.Create(); + rsa.ImportFromPem(publicKeyPem); + + // Verify signature + var signature = Base64UrlDecode(parts[1]); + return rsa.VerifyData(payloadBytes, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); + } + catch + { + return false; + } + + break; + default: + return false; + } + } + catch (Exception ex) + { + Logger.LogWarning(ex, "Token validation failed"); + return false; + } + } + + private static byte[] Base64UrlDecode(string base64Url) + { + var padded = base64Url + .Replace('-', '+') + .Replace('_', '/'); + + switch (padded.Length % 4) + { + case 2: padded += "=="; break; + case 3: padded += "="; break; + } + + return Convert.FromBase64String(padded); + } + + private TokenInfo? _ExtractToken(HttpRequest request) + { + // Check for token in query parameters + if (request.Query.TryGetValue(AuthConstants.TokenQueryParamName, out var queryToken)) + { + return new TokenInfo + { + Token = queryToken.ToString(), + Type = TokenType.AuthKey + }; + } + + + // Check for token in Authorization header + var authHeader = request.Headers.Authorization.ToString(); + if (!string.IsNullOrEmpty(authHeader)) + { + if (authHeader.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase)) + { + var token = authHeader["Bearer ".Length..].Trim(); + var parts = token.Split('.'); + + return new TokenInfo + { + Token = token, + Type = parts.Length == 3 ? TokenType.OidcKey : TokenType.AuthKey + }; + } + else if (authHeader.StartsWith("AtField ", StringComparison.OrdinalIgnoreCase)) + { + return new TokenInfo + { + Token = authHeader["AtField ".Length..].Trim(), + Type = TokenType.AuthKey + }; + } + else if (authHeader.StartsWith("AkField ", StringComparison.OrdinalIgnoreCase)) + { + return new TokenInfo + { + Token = authHeader["AkField ".Length..].Trim(), + Type = TokenType.ApiKey + }; + } + } + + // Check for token in cookies + if (request.Cookies.TryGetValue(AuthConstants.CookieTokenName, out var cookieToken)) + { + return new TokenInfo + { + Token = cookieToken, + Type = cookieToken.Count(c => c == '.') == 2 ? TokenType.OidcKey : TokenType.AuthKey + }; + } + + + return null; + } +} \ No newline at end of file diff --git a/DysonNetwork.Pass/Auth/AuthController.cs b/DysonNetwork.Pass/Auth/AuthController.cs new file mode 100644 index 0000000..517da48 --- /dev/null +++ b/DysonNetwork.Pass/Auth/AuthController.cs @@ -0,0 +1,266 @@ +using System.ComponentModel.DataAnnotations; +using Microsoft.AspNetCore.Mvc; +using NodaTime; +using Microsoft.EntityFrameworkCore; +using DysonNetwork.Pass.Account; +using DysonNetwork.Shared.Geo; + +namespace DysonNetwork.Pass.Auth; + +[ApiController] +[Route("/api/auth")] +public class AuthController( + AppDatabase db, + AccountService accounts, + AuthService auth, + GeoIpService geo, + ActionLogService als +) : ControllerBase +{ + public class ChallengeRequest + { + [Required] public ChallengePlatform Platform { get; set; } + [Required] [MaxLength(256)] public string Account { get; set; } = null!; + [Required] [MaxLength(512)] public string DeviceId { get; set; } = null!; + public List Audiences { get; set; } = new(); + public List Scopes { get; set; } = new(); + } + + [HttpPost("challenge")] + public async Task> StartChallenge([FromBody] ChallengeRequest request) + { + var account = await accounts.LookupAccount(request.Account); + if (account is null) return NotFound("Account was not found."); + + var ipAddress = HttpContext.Connection.RemoteIpAddress?.ToString(); + var userAgent = HttpContext.Request.Headers.UserAgent.ToString(); + + var now = Instant.FromDateTimeUtc(DateTime.UtcNow); + + // Trying to pick up challenges from the same IP address and user agent + var existingChallenge = await db.AuthChallenges + .Where(e => e.Account == account) + .Where(e => e.IpAddress == ipAddress) + .Where(e => e.UserAgent == userAgent) + .Where(e => e.StepRemain > 0) + .Where(e => e.ExpiredAt != null && now < e.ExpiredAt) + .FirstOrDefaultAsync(); + if (existingChallenge is not null) return existingChallenge; + + var challenge = new Challenge + { + ExpiredAt = Instant.FromDateTimeUtc(DateTime.UtcNow.AddHours(1)), + StepTotal = await auth.DetectChallengeRisk(Request, account), + Platform = request.Platform, + Audiences = request.Audiences, + Scopes = request.Scopes, + IpAddress = ipAddress, + UserAgent = userAgent, + Location = geo.GetPointFromIp(ipAddress), + DeviceId = request.DeviceId, + AccountId = account.Id + }.Normalize(); + + await db.AuthChallenges.AddAsync(challenge); + await db.SaveChangesAsync(); + + als.CreateActionLogFromRequest(ActionLogType.ChallengeAttempt, + new Dictionary { { "challenge_id", challenge.Id } }, Request, account + ); + + return challenge; + } + + [HttpGet("challenge/{id:guid}")] + public async Task> GetChallenge([FromRoute] Guid id) + { + var challenge = await db.AuthChallenges + .Include(e => e.Account) + .ThenInclude(e => e.Profile) + .FirstOrDefaultAsync(e => e.Id == id); + + return challenge is null + ? NotFound("Auth challenge was not found.") + : challenge; + } + + [HttpGet("challenge/{id:guid}/factors")] + public async Task>> GetChallengeFactors([FromRoute] Guid id) + { + var challenge = await db.AuthChallenges + .Include(e => e.Account) + .Include(e => e.Account.AuthFactors) + .Where(e => e.Id == id) + .FirstOrDefaultAsync(); + return challenge is null + ? NotFound("Auth challenge was not found.") + : challenge.Account.AuthFactors.Where(e => e is { EnabledAt: not null, Trustworthy: >= 1 }).ToList(); + } + + [HttpPost("challenge/{id:guid}/factors/{factorId:guid}")] + public async Task RequestFactorCode( + [FromRoute] Guid id, + [FromRoute] Guid factorId, + [FromBody] string? hint + ) + { + var challenge = await db.AuthChallenges + .Include(e => e.Account) + .Where(e => e.Id == id).FirstOrDefaultAsync(); + if (challenge is null) return NotFound("Auth challenge was not found."); + var factor = await db.AccountAuthFactors + .Where(e => e.Id == factorId) + .Where(e => e.Account == challenge.Account).FirstOrDefaultAsync(); + if (factor is null) return NotFound("Auth factor was not found."); + + try + { + await accounts.SendFactorCode(challenge.Account, factor, hint); + } + catch (Exception ex) + { + return BadRequest(ex.Message); + } + + return Ok(); + } + + public class PerformChallengeRequest + { + [Required] public Guid FactorId { get; set; } + [Required] public string Password { get; set; } = string.Empty; + } + + [HttpPatch("challenge/{id:guid}")] + public async Task> DoChallenge( + [FromRoute] Guid id, + [FromBody] PerformChallengeRequest request + ) + { + var challenge = await db.AuthChallenges.Include(e => e.Account).FirstOrDefaultAsync(e => e.Id == id); + if (challenge is null) return NotFound("Auth challenge was not found."); + + var factor = await db.AccountAuthFactors.FindAsync(request.FactorId); + if (factor is null) return NotFound("Auth factor was not found."); + if (factor.EnabledAt is null) return BadRequest("Auth factor is not enabled."); + if (factor.Trustworthy <= 0) return BadRequest("Auth factor is not trustworthy."); + + if (challenge.StepRemain == 0) return challenge; + if (challenge.ExpiredAt.HasValue && challenge.ExpiredAt.Value < Instant.FromDateTimeUtc(DateTime.UtcNow)) + return BadRequest(); + + try + { + if (await accounts.VerifyFactorCode(factor, request.Password)) + { + challenge.StepRemain -= factor.Trustworthy; + challenge.StepRemain = Math.Max(0, challenge.StepRemain); + challenge.BlacklistFactors.Add(factor.Id); + db.Update(challenge); + als.CreateActionLogFromRequest(ActionLogType.ChallengeSuccess, + new Dictionary + { + { "challenge_id", challenge.Id }, + { "factor_id", factor.Id } + }, Request, challenge.Account + ); + } + else + { + throw new ArgumentException("Invalid password."); + } + } + catch + { + challenge.FailedAttempts++; + db.Update(challenge); + als.CreateActionLogFromRequest(ActionLogType.ChallengeFailure, + new Dictionary + { + { "challenge_id", challenge.Id }, + { "factor_id", factor.Id } + }, Request, challenge.Account + ); + await db.SaveChangesAsync(); + return BadRequest("Invalid password."); + } + + if (challenge.StepRemain == 0) + { + als.CreateActionLogFromRequest(ActionLogType.NewLogin, + new Dictionary + { + { "challenge_id", challenge.Id }, + { "account_id", challenge.AccountId } + }, Request, challenge.Account + ); + } + + await db.SaveChangesAsync(); + return challenge; + } + + public class TokenExchangeRequest + { + public string GrantType { get; set; } = string.Empty; + public string? RefreshToken { get; set; } + public string? Code { get; set; } + } + + public class TokenExchangeResponse + { + public string Token { get; set; } = string.Empty; + } + + [HttpPost("token")] + public async Task> ExchangeToken([FromBody] TokenExchangeRequest request) + { + switch (request.GrantType) + { + case "authorization_code": + var code = Guid.TryParse(request.Code, out var codeId) ? codeId : Guid.Empty; + if (code == Guid.Empty) + return BadRequest("Invalid or missing authorization code."); + var challenge = await db.AuthChallenges + .Include(e => e.Account) + .Where(e => e.Id == code) + .FirstOrDefaultAsync(); + if (challenge is null) + return BadRequest("Authorization code not found or expired."); + if (challenge.StepRemain != 0) + return BadRequest("Challenge not yet completed."); + + var session = await db.AuthSessions + .Where(e => e.Challenge == challenge) + .FirstOrDefaultAsync(); + if (session is not null) + return BadRequest("Session already exists for this challenge."); + + session = new Session + { + LastGrantedAt = Instant.FromDateTimeUtc(DateTime.UtcNow), + ExpiredAt = Instant.FromDateTimeUtc(DateTime.UtcNow.AddDays(30)), + Account = challenge.Account, + Challenge = challenge, + }; + + db.AuthSessions.Add(session); + await db.SaveChangesAsync(); + + var tk = auth.CreateToken(session); + return Ok(new TokenExchangeResponse { Token = tk }); + case "refresh_token": + // Since we no longer need the refresh token + // This case is blank for now, thinking to mock it if the OIDC standard requires it + default: + return BadRequest("Unsupported grant type."); + } + } + + [HttpPost("captcha")] + public async Task ValidateCaptcha([FromBody] string token) + { + var result = await auth.ValidateCaptcha(token); + return result ? Ok() : BadRequest(); + } +} \ No newline at end of file diff --git a/DysonNetwork.Pass/Auth/AuthService.cs b/DysonNetwork.Pass/Auth/AuthService.cs new file mode 100644 index 0000000..7488b3c --- /dev/null +++ b/DysonNetwork.Pass/Auth/AuthService.cs @@ -0,0 +1,304 @@ +using System.Security.Cryptography; +using System.Text.Json; +using DysonNetwork.Pass.Account; +using DysonNetwork.Shared.Cache; +using Microsoft.EntityFrameworkCore; +using NodaTime; + +namespace DysonNetwork.Pass.Auth; + +public class AuthService( + AppDatabase db, + IConfiguration config, + IHttpClientFactory httpClientFactory, + IHttpContextAccessor httpContextAccessor, + ICacheService cache +) +{ + private HttpContext HttpContext => httpContextAccessor.HttpContext!; + + /// + /// Detect the risk of the current request to login + /// and returns the required steps to login. + /// + /// The request context + /// The account to login + /// The required steps to login + public async Task DetectChallengeRisk(HttpRequest request, Account.Account account) + { + // 1) Find out how many authentication factors the account has enabled. + var maxSteps = await db.AccountAuthFactors + .Where(f => f.AccountId == account.Id) + .Where(f => f.EnabledAt != null) + .CountAsync(); + + // We’ll accumulate a “risk score” based on various factors. + // Then we can decide how many total steps are required for the challenge. + var riskScore = 0; + + // 2) Get the remote IP address from the request (if any). + var ipAddress = request.HttpContext.Connection.RemoteIpAddress?.ToString(); + var lastActiveInfo = await db.AuthSessions + .OrderByDescending(s => s.LastGrantedAt) + .Include(s => s.Challenge) + .Where(s => s.AccountId == account.Id) + .FirstOrDefaultAsync(); + + // Example check: if IP is missing or in an unusual range, increase the risk. + // (This is just a placeholder; in reality, you’d integrate with GeoIpService or a custom check.) + if (string.IsNullOrWhiteSpace(ipAddress)) + riskScore += 1; + else + { + if (!string.IsNullOrEmpty(lastActiveInfo?.Challenge.IpAddress) && + !lastActiveInfo.Challenge.IpAddress.Equals(ipAddress, StringComparison.OrdinalIgnoreCase)) + riskScore += 1; + } + + // 3) (Optional) Check how recent the last login was. + // If it was a long time ago, the risk might be higher. + var now = SystemClock.Instance.GetCurrentInstant(); + var daysSinceLastActive = lastActiveInfo?.LastGrantedAt is not null + ? (now - lastActiveInfo.LastGrantedAt.Value).TotalDays + : double.MaxValue; + if (daysSinceLastActive > 30) + riskScore += 1; + + // 4) Combine base “maxSteps” (the number of enabled factors) with any accumulated risk score. + const int totalRiskScore = 3; + var totalRequiredSteps = (int)Math.Round((float)maxSteps * riskScore / totalRiskScore); + // Clamp the steps + totalRequiredSteps = Math.Max(Math.Min(totalRequiredSteps, maxSteps), 1); + + return totalRequiredSteps; + } + + public async Task CreateSessionForOidcAsync(Account.Account account, Instant time, Guid? customAppId = null) + { + var challenge = new Challenge + { + 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 + }; + + var session = new Session + { + AccountId = account.Id, + CreatedAt = time, + LastGrantedAt = time, + Challenge = challenge, + AppId = customAppId + }; + + db.AuthChallenges.Add(challenge); + db.AuthSessions.Add(session); + await db.SaveChangesAsync(); + + return session; + } + + public async Task ValidateCaptcha(string token) + { + if (string.IsNullOrWhiteSpace(token)) return false; + + var provider = config.GetSection("Captcha")["Provider"]?.ToLower(); + var apiSecret = config.GetSection("Captcha")["ApiSecret"]; + + var client = httpClientFactory.CreateClient(); + + var jsonOpts = new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower, + DictionaryKeyPolicy = JsonNamingPolicy.SnakeCaseLower + }; + + switch (provider) + { + case "cloudflare": + var content = new StringContent($"secret={apiSecret}&response={token}", System.Text.Encoding.UTF8, + "application/x-www-form-urlencoded"); + var response = await client.PostAsync("https://challenges.cloudflare.com/turnstile/v0/siteverify", + content); + response.EnsureSuccessStatusCode(); + + var json = await response.Content.ReadAsStringAsync(); + var result = JsonSerializer.Deserialize(json, options: jsonOpts); + + return result?.Success == true; + case "google": + content = new StringContent($"secret={apiSecret}&response={token}", System.Text.Encoding.UTF8, + "application/x-www-form-urlencoded"); + response = await client.PostAsync("https://www.google.com/recaptcha/siteverify", content); + response.EnsureSuccessStatusCode(); + + json = await response.Content.ReadAsStringAsync(); + result = JsonSerializer.Deserialize(json, options: jsonOpts); + + return result?.Success == true; + case "hcaptcha": + content = new StringContent($"secret={apiSecret}&response={token}", System.Text.Encoding.UTF8, + "application/x-www-form-urlencoded"); + response = await client.PostAsync("https://hcaptcha.com/siteverify", content); + response.EnsureSuccessStatusCode(); + + json = await response.Content.ReadAsStringAsync(); + result = JsonSerializer.Deserialize(json, options: jsonOpts); + + return result?.Success == true; + default: + throw new ArgumentException("The server misconfigured for the captcha."); + } + } + + public string CreateToken(Session session) + { + // Load the private key for signing + var privateKeyPem = File.ReadAllText(config["AuthToken:PrivateKeyPath"]!); + using var rsa = RSA.Create(); + rsa.ImportFromPem(privateKeyPem); + + // Create and return a single token + return CreateCompactToken(session.Id, rsa); + } + + private string CreateCompactToken(Guid sessionId, RSA rsa) + { + // Create the payload: just the session ID + var payloadBytes = sessionId.ToByteArray(); + + // Base64Url encode the payload + var payloadBase64 = Base64UrlEncode(payloadBytes); + + // Sign the payload with RSA-SHA256 + var signature = rsa.SignData(payloadBytes, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); + + // Base64Url encode the signature + var signatureBase64 = Base64UrlEncode(signature); + + // Combine payload and signature with a dot + return $"{payloadBase64}.{signatureBase64}"; + } + + public async Task ValidateSudoMode(Session session, string? pinCode) + { + // Check if the session is already in sudo mode (cached) + var sudoModeKey = $"accounts:{session.Id}:sudo"; + var (found, _) = await cache.GetAsyncWithStatus(sudoModeKey); + + if (found) + { + // Session is already in sudo mode + return true; + } + + // Check if the user has a pin code + var hasPinCode = await db.AccountAuthFactors + .Where(f => f.AccountId == session.AccountId) + .Where(f => f.EnabledAt != null) + .Where(f => f.Type == AccountAuthFactorType.PinCode) + .AnyAsync(); + + if (!hasPinCode) + { + // User doesn't have a pin code, no validation needed + return true; + } + + // If pin code is not provided, we can't validate + if (string.IsNullOrEmpty(pinCode)) + { + return false; + } + + try + { + // Validate the pin code + var isValid = await ValidatePinCode(session.AccountId, pinCode); + + if (isValid) + { + // Set session in sudo mode for 5 minutes + await cache.SetAsync(sudoModeKey, true, TimeSpan.FromMinutes(5)); + } + + return isValid; + } + catch (InvalidOperationException) + { + // No pin code enabled for this account, so validation is successful + return true; + } + } + + public async Task ValidatePinCode(Guid accountId, string pinCode) + { + var factor = await db.AccountAuthFactors + .Where(f => f.AccountId == accountId) + .Where(f => f.EnabledAt != null) + .Where(f => f.Type == AccountAuthFactorType.PinCode) + .FirstOrDefaultAsync(); + if (factor is null) throw new InvalidOperationException("No pin code enabled for this account."); + + return factor.VerifyPassword(pinCode); + } + + public bool ValidateToken(string token, out Guid sessionId) + { + sessionId = Guid.Empty; + + try + { + // Split the token + var parts = token.Split('.'); + if (parts.Length != 2) + return false; + + // Decode the payload + var payloadBytes = Base64UrlDecode(parts[0]); + + // Extract session ID + sessionId = new Guid(payloadBytes); + + // Load public key for verification + var publicKeyPem = File.ReadAllText(config["AuthToken:PublicKeyPath"]!); + using var rsa = RSA.Create(); + rsa.ImportFromPem(publicKeyPem); + + // Verify signature + var signature = Base64UrlDecode(parts[1]); + return rsa.VerifyData(payloadBytes, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); + } + catch + { + return false; + } + } + + // Helper methods for Base64Url encoding/decoding + private static string Base64UrlEncode(byte[] data) + { + return Convert.ToBase64String(data) + .TrimEnd('=') + .Replace('+', '-') + .Replace('/', '_'); + } + + private static byte[] Base64UrlDecode(string base64Url) + { + string padded = base64Url + .Replace('-', '+') + .Replace('_', '/'); + + switch (padded.Length % 4) + { + case 2: padded += "=="; break; + case 3: padded += "="; break; + } + + return Convert.FromBase64String(padded); + } +} \ No newline at end of file diff --git a/DysonNetwork.Pass/Auth/CheckpointModel.cs b/DysonNetwork.Pass/Auth/CheckpointModel.cs new file mode 100644 index 0000000..58466e9 --- /dev/null +++ b/DysonNetwork.Pass/Auth/CheckpointModel.cs @@ -0,0 +1,6 @@ +namespace DysonNetwork.Pass.Auth; + +public class CaptchaVerificationResponse +{ + public bool Success { get; set; } +} \ No newline at end of file diff --git a/DysonNetwork.Pass/Auth/CompactTokenService.cs b/DysonNetwork.Pass/Auth/CompactTokenService.cs new file mode 100644 index 0000000..3ef9742 --- /dev/null +++ b/DysonNetwork.Pass/Auth/CompactTokenService.cs @@ -0,0 +1,94 @@ +using System.Security.Cryptography; + +namespace DysonNetwork.Pass.Auth; + +public class CompactTokenService(IConfiguration config) +{ + private readonly string _privateKeyPath = config["AuthToken:PrivateKeyPath"] + ?? throw new InvalidOperationException("AuthToken:PrivateKeyPath configuration is missing"); + + public string CreateToken(Session session) + { + // Load the private key for signing + var privateKeyPem = File.ReadAllText(_privateKeyPath); + using var rsa = RSA.Create(); + rsa.ImportFromPem(privateKeyPem); + + // Create and return a single token + return CreateCompactToken(session.Id, rsa); + } + + private string CreateCompactToken(Guid sessionId, RSA rsa) + { + // Create the payload: just the session ID + var payloadBytes = sessionId.ToByteArray(); + + // Base64Url encode the payload + var payloadBase64 = Base64UrlEncode(payloadBytes); + + // Sign the payload with RSA-SHA256 + var signature = rsa.SignData(payloadBytes, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); + + // Base64Url encode the signature + var signatureBase64 = Base64UrlEncode(signature); + + // Combine payload and signature with a dot + return $"{payloadBase64}.{signatureBase64}"; + } + + public bool ValidateToken(string token, out Guid sessionId) + { + sessionId = Guid.Empty; + + try + { + // Split the token + var parts = token.Split('.'); + if (parts.Length != 2) + return false; + + // Decode the payload + var payloadBytes = Base64UrlDecode(parts[0]); + + // Extract session ID + sessionId = new Guid(payloadBytes); + + // Load public key for verification + var publicKeyPem = File.ReadAllText(config["AuthToken:PublicKeyPath"]!); + using var rsa = RSA.Create(); + rsa.ImportFromPem(publicKeyPem); + + // Verify signature + var signature = Base64UrlDecode(parts[1]); + return rsa.VerifyData(payloadBytes, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); + } + catch + { + return false; + } + } + + // Helper methods for Base64Url encoding/decoding + private static string Base64UrlEncode(byte[] data) + { + return Convert.ToBase64String(data) + .TrimEnd('=') + .Replace('+', '-') + .Replace('/', '_'); + } + + private static byte[] Base64UrlDecode(string base64Url) + { + string padded = base64Url + .Replace('-', '+') + .Replace('_', '/'); + + switch (padded.Length % 4) + { + case 2: padded += "=="; break; + case 3: padded += "="; break; + } + + return Convert.FromBase64String(padded); + } +} \ No newline at end of file diff --git a/DysonNetwork.Pass/Auth/OidcProvider/Controllers/OidcProviderController.cs b/DysonNetwork.Pass/Auth/OidcProvider/Controllers/OidcProviderController.cs new file mode 100644 index 0000000..c033b93 --- /dev/null +++ b/DysonNetwork.Pass/Auth/OidcProvider/Controllers/OidcProviderController.cs @@ -0,0 +1,241 @@ +using System.Security.Cryptography; +using DysonNetwork.Pass.Auth.OidcProvider.Responses; +using DysonNetwork.Pass.Auth.OidcProvider.Services; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Options; +using System.Text.Json.Serialization; +using DysonNetwork.Pass.Account; +using DysonNetwork.Pass.Auth.OidcProvider.Options; +using Microsoft.EntityFrameworkCore; +using Microsoft.IdentityModel.Tokens; +using NodaTime; + +namespace DysonNetwork.Pass.Auth.OidcProvider.Controllers; + +[Route("/api/auth/open")] +[ApiController] +public class OidcProviderController( + AppDatabase db, + OidcProviderService oidcService, + IConfiguration configuration, + IOptions options, + ILogger logger +) + : ControllerBase +{ + [HttpPost("token")] + [Consumes("application/x-www-form-urlencoded")] + public async Task Token([FromForm] TokenRequest request) + { + switch (request.GrantType) + { + // Validate client credentials + case "authorization_code" when request.ClientId == null || string.IsNullOrEmpty(request.ClientSecret): + return BadRequest("Client credentials are required"); + case "authorization_code" when request.Code == null: + return BadRequest("Authorization code is required"); + case "authorization_code": + { + var client = await oidcService.FindClientByIdAsync(request.ClientId.Value); + if (client == null || + !await oidcService.ValidateClientCredentialsAsync(request.ClientId.Value, request.ClientSecret)) + return BadRequest(new ErrorResponse + { Error = "invalid_client", ErrorDescription = "Invalid client credentials" }); + + // Generate tokens + var tokenResponse = await oidcService.GenerateTokenResponseAsync( + clientId: request.ClientId.Value, + authorizationCode: request.Code!, + redirectUri: request.RedirectUri, + codeVerifier: request.CodeVerifier + ); + + return Ok(tokenResponse); + } + case "refresh_token" when string.IsNullOrEmpty(request.RefreshToken): + return BadRequest(new ErrorResponse + { Error = "invalid_request", ErrorDescription = "Refresh token is required" }); + case "refresh_token": + { + try + { + // Decode the base64 refresh token to get the session ID + var sessionIdBytes = Convert.FromBase64String(request.RefreshToken); + var sessionId = new Guid(sessionIdBytes); + + // Find the session and related data + var session = await oidcService.FindSessionByIdAsync(sessionId); + var now = SystemClock.Instance.GetCurrentInstant(); + if (session?.App is null || session.ExpiredAt < now) + { + return BadRequest(new ErrorResponse + { + Error = "invalid_grant", + ErrorDescription = "Invalid or expired refresh token" + }); + } + + // Get the client + var client = session.App; + if (client == null) + { + return BadRequest(new ErrorResponse + { + Error = "invalid_client", + ErrorDescription = "Client not found" + }); + } + + // Generate new tokens + var tokenResponse = await oidcService.GenerateTokenResponseAsync( + clientId: session.AppId!.Value, + sessionId: session.Id + ); + + return Ok(tokenResponse); + } + catch (FormatException) + { + return BadRequest(new ErrorResponse + { + Error = "invalid_grant", + ErrorDescription = "Invalid refresh token format" + }); + } + } + default: + return BadRequest(new ErrorResponse { Error = "unsupported_grant_type" }); + } + } + + [HttpGet("userinfo")] + [Authorize] + public async Task GetUserInfo() + { + if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser || + HttpContext.Items["CurrentSession"] is not Session currentSession) return Unauthorized(); + + // Get requested scopes from the token + var scopes = currentSession.Challenge.Scopes; + + var userInfo = new Dictionary + { + ["sub"] = currentUser.Id + }; + + // Include standard claims based on scopes + if (scopes.Contains("profile") || scopes.Contains("name")) + { + userInfo["name"] = currentUser.Name; + userInfo["preferred_username"] = currentUser.Nick; + } + + var userEmail = await db.AccountContacts + .Where(c => c.Type == AccountContactType.Email && c.AccountId == currentUser.Id) + .FirstOrDefaultAsync(); + if (scopes.Contains("email") && userEmail is not null) + { + userInfo["email"] = userEmail.Content; + userInfo["email_verified"] = userEmail.VerifiedAt is not null; + } + + return Ok(userInfo); + } + + [HttpGet("/.well-known/openid-configuration")] + public IActionResult GetConfiguration() + { + var baseUrl = configuration["BaseUrl"]; + var issuer = options.Value.IssuerUri.TrimEnd('/'); + + return Ok(new + { + issuer = issuer, + authorization_endpoint = $"{baseUrl}/auth/authorize", + token_endpoint = $"{baseUrl}/auth/open/token", + userinfo_endpoint = $"{baseUrl}/auth/open/userinfo", + jwks_uri = $"{baseUrl}/.well-known/jwks", + scopes_supported = new[] { "openid", "profile", "email" }, + response_types_supported = new[] + { "code", "token", "id_token", "code token", "code id_token", "token id_token", "code token id_token" }, + grant_types_supported = new[] { "authorization_code", "refresh_token" }, + token_endpoint_auth_methods_supported = new[] { "client_secret_basic", "client_secret_post" }, + id_token_signing_alg_values_supported = new[] { "HS256" }, + subject_types_supported = new[] { "public" }, + claims_supported = new[] { "sub", "name", "email", "email_verified" }, + code_challenge_methods_supported = new[] { "S256" }, + response_modes_supported = new[] { "query", "fragment", "form_post" }, + request_parameter_supported = true, + request_uri_parameter_supported = true, + require_request_uri_registration = false + }); + } + + [HttpGet("/.well-known/jwks")] + public IActionResult GetJwks() + { + using var rsa = options.Value.GetRsaPublicKey(); + if (rsa == null) + { + return BadRequest("Public key is not configured"); + } + + var parameters = rsa.ExportParameters(false); + var keyId = Convert.ToBase64String(SHA256.HashData(parameters.Modulus!)[..8]) + .Replace("+", "-") + .Replace("/", "_") + .Replace("=", ""); + + return Ok(new + { + keys = new[] + { + new + { + kty = "RSA", + use = "sig", + kid = keyId, + n = Base64UrlEncoder.Encode(parameters.Modulus!), + e = Base64UrlEncoder.Encode(parameters.Exponent!), + alg = "RS256" + } + } + }); + } +} + +public class TokenRequest +{ + [JsonPropertyName("grant_type")] + [FromForm(Name = "grant_type")] + public string? GrantType { get; set; } + + [JsonPropertyName("code")] + [FromForm(Name = "code")] + public string? Code { get; set; } + + [JsonPropertyName("redirect_uri")] + [FromForm(Name = "redirect_uri")] + public string? RedirectUri { get; set; } + + [JsonPropertyName("client_id")] + [FromForm(Name = "client_id")] + public Guid? ClientId { get; set; } + + [JsonPropertyName("client_secret")] + [FromForm(Name = "client_secret")] + public string? ClientSecret { get; set; } + + [JsonPropertyName("refresh_token")] + [FromForm(Name = "refresh_token")] + public string? RefreshToken { get; set; } + + [JsonPropertyName("scope")] + [FromForm(Name = "scope")] + public string? Scope { get; set; } + + [JsonPropertyName("code_verifier")] + [FromForm(Name = "code_verifier")] + public string? CodeVerifier { get; set; } +} \ No newline at end of file diff --git a/DysonNetwork.Pass/Auth/OidcProvider/Models/AuthorizationCodeInfo.cs b/DysonNetwork.Pass/Auth/OidcProvider/Models/AuthorizationCodeInfo.cs new file mode 100644 index 0000000..4f8ca5d --- /dev/null +++ b/DysonNetwork.Pass/Auth/OidcProvider/Models/AuthorizationCodeInfo.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using NodaTime; + +namespace DysonNetwork.Pass.Auth.OidcProvider.Models; + +public class AuthorizationCodeInfo +{ + public Guid ClientId { get; set; } + public Guid AccountId { get; set; } + public string RedirectUri { get; set; } = string.Empty; + public List Scopes { get; set; } = new(); + public string? CodeChallenge { get; set; } + public string? CodeChallengeMethod { get; set; } + public string? Nonce { get; set; } + public Instant CreatedAt { get; set; } +} diff --git a/DysonNetwork.Pass/Auth/OidcProvider/Options/OidcProviderOptions.cs b/DysonNetwork.Pass/Auth/OidcProvider/Options/OidcProviderOptions.cs new file mode 100644 index 0000000..40397a5 --- /dev/null +++ b/DysonNetwork.Pass/Auth/OidcProvider/Options/OidcProviderOptions.cs @@ -0,0 +1,36 @@ +using System.Security.Cryptography; + +namespace DysonNetwork.Pass.Auth.OidcProvider.Options; + +public class OidcProviderOptions +{ + public string IssuerUri { get; set; } = "https://your-issuer-uri.com"; + public string? PublicKeyPath { get; set; } + public string? PrivateKeyPath { get; set; } + public TimeSpan AccessTokenLifetime { get; set; } = TimeSpan.FromHours(1); + public TimeSpan RefreshTokenLifetime { get; set; } = TimeSpan.FromDays(30); + public TimeSpan AuthorizationCodeLifetime { get; set; } = TimeSpan.FromMinutes(5); + public bool RequireHttpsMetadata { get; set; } = true; + + public RSA? GetRsaPrivateKey() + { + if (string.IsNullOrEmpty(PrivateKeyPath) || !File.Exists(PrivateKeyPath)) + return null; + + var privateKey = File.ReadAllText(PrivateKeyPath); + var rsa = RSA.Create(); + rsa.ImportFromPem(privateKey.AsSpan()); + return rsa; + } + + public RSA? GetRsaPublicKey() + { + if (string.IsNullOrEmpty(PublicKeyPath) || !File.Exists(PublicKeyPath)) + return null; + + var publicKey = File.ReadAllText(PublicKeyPath); + var rsa = RSA.Create(); + rsa.ImportFromPem(publicKey.AsSpan()); + return rsa; + } +} \ No newline at end of file diff --git a/DysonNetwork.Pass/Auth/OidcProvider/Responses/AuthorizationResponse.cs b/DysonNetwork.Pass/Auth/OidcProvider/Responses/AuthorizationResponse.cs new file mode 100644 index 0000000..407520b --- /dev/null +++ b/DysonNetwork.Pass/Auth/OidcProvider/Responses/AuthorizationResponse.cs @@ -0,0 +1,23 @@ +using System.Text.Json.Serialization; + +namespace DysonNetwork.Pass.Auth.OidcProvider.Responses; + +public class AuthorizationResponse +{ + [JsonPropertyName("code")] + public string Code { get; set; } = null!; + + [JsonPropertyName("state")] + public string? State { get; set; } + + [JsonPropertyName("scope")] + public string? Scope { get; set; } + + + [JsonPropertyName("session_state")] + public string? SessionState { get; set; } + + + [JsonPropertyName("iss")] + public string? Issuer { get; set; } +} diff --git a/DysonNetwork.Pass/Auth/OidcProvider/Responses/ErrorResponse.cs b/DysonNetwork.Pass/Auth/OidcProvider/Responses/ErrorResponse.cs new file mode 100644 index 0000000..b72a4a7 --- /dev/null +++ b/DysonNetwork.Pass/Auth/OidcProvider/Responses/ErrorResponse.cs @@ -0,0 +1,20 @@ +using System.Text.Json.Serialization; + +namespace DysonNetwork.Pass.Auth.OidcProvider.Responses; + +public class ErrorResponse +{ + [JsonPropertyName("error")] + public string Error { get; set; } = null!; + + [JsonPropertyName("error_description")] + public string? ErrorDescription { get; set; } + + + [JsonPropertyName("error_uri")] + public string? ErrorUri { get; set; } + + + [JsonPropertyName("state")] + public string? State { get; set; } +} diff --git a/DysonNetwork.Pass/Auth/OidcProvider/Responses/TokenResponse.cs b/DysonNetwork.Pass/Auth/OidcProvider/Responses/TokenResponse.cs new file mode 100644 index 0000000..3c7e50c --- /dev/null +++ b/DysonNetwork.Pass/Auth/OidcProvider/Responses/TokenResponse.cs @@ -0,0 +1,26 @@ +using System.Text.Json.Serialization; + +namespace DysonNetwork.Pass.Auth.OidcProvider.Responses; + +public class TokenResponse +{ + [JsonPropertyName("access_token")] + public string AccessToken { get; set; } = null!; + + [JsonPropertyName("expires_in")] + public int ExpiresIn { get; set; } + + [JsonPropertyName("token_type")] + public string TokenType { get; set; } = "Bearer"; + + [JsonPropertyName("refresh_token")] + public string? RefreshToken { get; set; } + + + [JsonPropertyName("scope")] + public string? Scope { get; set; } + + + [JsonPropertyName("id_token")] + public string? IdToken { get; set; } +} diff --git a/DysonNetwork.Pass/Auth/OidcProvider/Services/OidcProviderService.cs b/DysonNetwork.Pass/Auth/OidcProvider/Services/OidcProviderService.cs new file mode 100644 index 0000000..a4d5968 --- /dev/null +++ b/DysonNetwork.Pass/Auth/OidcProvider/Services/OidcProviderService.cs @@ -0,0 +1,394 @@ +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Security.Cryptography; +using System.Text; +using DysonNetwork.Pass.Auth.OidcProvider.Models; +using DysonNetwork.Pass.Auth.OidcProvider.Options; +using DysonNetwork.Pass.Auth.OidcProvider.Responses; +using DysonNetwork.Shared.Cache; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Options; +using Microsoft.IdentityModel.Tokens; +using NodaTime; + +namespace DysonNetwork.Pass.Auth.OidcProvider.Services; + +public class OidcProviderService( + AppDatabase db, + AuthService auth, + ICacheService cache, + IOptions options, + ILogger logger +) +{ + private readonly OidcProviderOptions _options = options.Value; + + public async Task FindClientByIdAsync(Guid clientId) + { + return await db.CustomApps + .Include(c => c.Secrets) + .FirstOrDefaultAsync(c => c.Id == clientId); + } + + public async Task FindClientByAppIdAsync(Guid appId) + { + return await db.CustomApps + .Include(c => c.Secrets) + .FirstOrDefaultAsync(c => c.Id == appId); + } + + public async Task FindValidSessionAsync(Guid accountId, Guid clientId) + { + var now = SystemClock.Instance.GetCurrentInstant(); + + return await db.AuthSessions + .Include(s => s.Challenge) + .Where(s => s.AccountId == accountId && + s.AppId == clientId && + (s.ExpiredAt == null || s.ExpiredAt > now) && + s.Challenge.Type == ChallengeType.OAuth) + .OrderByDescending(s => s.CreatedAt) + .FirstOrDefaultAsync(); + } + + public async Task ValidateClientCredentialsAsync(Guid clientId, string clientSecret) + { + var client = await FindClientByIdAsync(clientId); + if (client == null) return false; + + var clock = SystemClock.Instance; + var secret = client.Secrets + .Where(s => s.IsOidc && (s.ExpiredAt == null || s.ExpiredAt > clock.GetCurrentInstant())) + .FirstOrDefault(s => s.Secret == clientSecret); // In production, use proper hashing + + return secret != null; + } + + public async Task GenerateTokenResponseAsync( + Guid clientId, + string? authorizationCode = null, + string? redirectUri = null, + string? codeVerifier = null, + Guid? sessionId = null + ) + { + var client = await FindClientByIdAsync(clientId); + if (client == null) + throw new InvalidOperationException("Client not found"); + + Session session; + var clock = SystemClock.Instance; + var now = clock.GetCurrentInstant(); + + List? scopes = null; + if (authorizationCode != null) + { + // Authorization code flow + var authCode = await ValidateAuthorizationCodeAsync(authorizationCode, clientId, redirectUri, codeVerifier); + if (authCode is null) throw new InvalidOperationException("Invalid authorization code"); + var account = await db.Accounts.Where(a => a.Id == authCode.AccountId).FirstOrDefaultAsync(); + if (account is null) throw new InvalidOperationException("Account was not found"); + + session = await auth.CreateSessionForOidcAsync(account, now, client.Id); + scopes = authCode.Scopes; + } + else if (sessionId.HasValue) + { + // Refresh token flow + session = await FindSessionByIdAsync(sessionId.Value) ?? + throw new InvalidOperationException("Invalid session"); + + // Verify the session is still valid + if (session.ExpiredAt < now) + throw new InvalidOperationException("Session has expired"); + } + else + { + throw new InvalidOperationException("Either authorization code or session ID must be provided"); + } + + var expiresIn = (int)_options.AccessTokenLifetime.TotalSeconds; + var expiresAt = now.Plus(Duration.FromSeconds(expiresIn)); + + // Generate an access token + var accessToken = GenerateJwtToken(client, session, expiresAt, scopes); + var refreshToken = GenerateRefreshToken(session); + + return new TokenResponse + { + AccessToken = accessToken, + ExpiresIn = expiresIn, + TokenType = "Bearer", + RefreshToken = refreshToken, + Scope = scopes != null ? string.Join(" ", scopes) : null + }; + } + + private string GenerateJwtToken( + CustomApp client, + Session session, + Instant expiresAt, + IEnumerable? scopes = null + ) + { + var tokenHandler = new JwtSecurityTokenHandler(); + var clock = SystemClock.Instance; + var now = clock.GetCurrentInstant(); + + var tokenDescriptor = new SecurityTokenDescriptor + { + Subject = new ClaimsIdentity([ + new Claim(JwtRegisteredClaimNames.Sub, session.AccountId.ToString()), + new Claim(JwtRegisteredClaimNames.Jti, session.Id.ToString()), + new Claim(JwtRegisteredClaimNames.Iat, now.ToUnixTimeSeconds().ToString(), + ClaimValueTypes.Integer64), + new Claim("client_id", client.Id.ToString()) + ]), + Expires = expiresAt.ToDateTimeUtc(), + Issuer = _options.IssuerUri, + Audience = client.Id.ToString() + }; + + // Try to use RSA signing if keys are available, fall back to HMAC + var rsaPrivateKey = _options.GetRsaPrivateKey(); + tokenDescriptor.SigningCredentials = new SigningCredentials( + new RsaSecurityKey(rsaPrivateKey), + SecurityAlgorithms.RsaSha256 + ); + + // Add scopes as claims if provided + var effectiveScopes = scopes?.ToList() ?? client.OauthConfig!.AllowedScopes?.ToList() ?? []; + if (effectiveScopes.Count != 0) + { + tokenDescriptor.Subject.AddClaims( + effectiveScopes.Select(scope => new Claim("scope", scope))); + } + + var token = tokenHandler.CreateToken(tokenDescriptor); + return tokenHandler.WriteToken(token); + } + + public (bool isValid, JwtSecurityToken? token) ValidateToken(string token) + { + try + { + var tokenHandler = new JwtSecurityTokenHandler(); + var validationParameters = new TokenValidationParameters + { + ValidateIssuer = true, + ValidIssuer = _options.IssuerUri, + ValidateAudience = false, + ValidateLifetime = true, + ClockSkew = TimeSpan.Zero + }; + + // Try to use RSA validation if public key is available + var rsaPublicKey = _options.GetRsaPublicKey(); + validationParameters.IssuerSigningKey = new RsaSecurityKey(rsaPublicKey); + validationParameters.ValidateIssuerSigningKey = true; + validationParameters.ValidAlgorithms = new[] { SecurityAlgorithms.RsaSha256 }; + + + tokenHandler.ValidateToken(token, validationParameters, out var validatedToken); + return (true, (JwtSecurityToken)validatedToken); + } + catch (Exception ex) + { + logger.LogError(ex, "Token validation failed"); + return (false, null); + } + } + + public async Task FindSessionByIdAsync(Guid sessionId) + { + return await db.AuthSessions + .Include(s => s.Account) + .Include(s => s.Challenge) + .Include(s => s.App) + .FirstOrDefaultAsync(s => s.Id == sessionId); + } + + private static string GenerateRefreshToken(Session session) + { + return Convert.ToBase64String(session.Id.ToByteArray()); + } + + private static bool VerifyHashedSecret(string secret, string hashedSecret) + { + // In a real implementation, you'd use a proper password hashing algorithm like PBKDF2, bcrypt, or Argon2 + // For now, we'll do a simple comparison, but you should replace this with proper hashing + return string.Equals(secret, hashedSecret, StringComparison.Ordinal); + } + + public async Task GenerateAuthorizationCodeForReuseSessionAsync( + Session session, + Guid clientId, + string redirectUri, + IEnumerable scopes, + string? codeChallenge = null, + string? codeChallengeMethod = null, + string? nonce = null) + { + var clock = SystemClock.Instance; + var now = clock.GetCurrentInstant(); + var code = Guid.NewGuid().ToString("N"); + + // Update the session's last activity time + await db.AuthSessions.Where(s => s.Id == session.Id) + .ExecuteUpdateAsync(s => s.SetProperty(s => s.LastGrantedAt, now)); + + // Create the authorization code info + var authCodeInfo = new AuthorizationCodeInfo + { + ClientId = clientId, + AccountId = session.AccountId, + RedirectUri = redirectUri, + Scopes = scopes.ToList(), + CodeChallenge = codeChallenge, + CodeChallengeMethod = codeChallengeMethod, + Nonce = nonce, + CreatedAt = now + }; + + // Store the code with its metadata in the cache + var cacheKey = $"auth:code:{code}"; + await cache.SetAsync(cacheKey, authCodeInfo, _options.AuthorizationCodeLifetime); + + logger.LogInformation("Generated authorization code for client {ClientId} and user {UserId}", clientId, session.AccountId); + return code; + } + + public async Task GenerateAuthorizationCodeAsync( + Guid clientId, + Guid userId, + string redirectUri, + IEnumerable scopes, + string? codeChallenge = null, + string? codeChallengeMethod = null, + string? nonce = null + ) + { + // Generate a random code + var clock = SystemClock.Instance; + var code = GenerateRandomString(32); + var now = clock.GetCurrentInstant(); + + // Create the authorization code info + var authCodeInfo = new AuthorizationCodeInfo + { + ClientId = clientId, + AccountId = userId, + RedirectUri = redirectUri, + Scopes = scopes.ToList(), + CodeChallenge = codeChallenge, + CodeChallengeMethod = codeChallengeMethod, + Nonce = nonce, + CreatedAt = now + }; + + // Store the code with its metadata in the cache + var cacheKey = $"auth:code:{code}"; + await cache.SetAsync(cacheKey, authCodeInfo, _options.AuthorizationCodeLifetime); + + logger.LogInformation("Generated authorization code for client {ClientId} and user {UserId}", clientId, userId); + return code; + } + + private async Task ValidateAuthorizationCodeAsync( + string code, + Guid clientId, + string? redirectUri = null, + string? codeVerifier = null + ) + { + var cacheKey = $"auth:code:{code}"; + var (found, authCode) = await cache.GetAsyncWithStatus(cacheKey); + + if (!found || authCode == null) + { + logger.LogWarning("Authorization code not found: {Code}", code); + return null; + } + + // Verify client ID matches + if (authCode.ClientId != clientId) + { + logger.LogWarning( + "Client ID mismatch for code {Code}. Expected: {ExpectedClientId}, Actual: {ActualClientId}", + code, authCode.ClientId, clientId); + return null; + } + + // Verify redirect URI if provided + if (!string.IsNullOrEmpty(redirectUri) && authCode.RedirectUri != redirectUri) + { + logger.LogWarning("Redirect URI mismatch for code {Code}", code); + return null; + } + + // Verify PKCE code challenge if one was provided during authorization + if (!string.IsNullOrEmpty(authCode.CodeChallenge)) + { + if (string.IsNullOrEmpty(codeVerifier)) + { + logger.LogWarning("PKCE code verifier is required but not provided for code {Code}", code); + return null; + } + + var isValid = authCode.CodeChallengeMethod?.ToUpperInvariant() switch + { + "S256" => VerifyCodeChallenge(codeVerifier, authCode.CodeChallenge, "S256"), + "PLAIN" => VerifyCodeChallenge(codeVerifier, authCode.CodeChallenge, "PLAIN"), + _ => false // Unsupported code challenge method + }; + + if (!isValid) + { + logger.LogWarning("PKCE code verifier validation failed for code {Code}", code); + return null; + } + } + + // Code is valid, remove it from the cache (codes are single-use) + await cache.RemoveAsync(cacheKey); + + return authCode; + } + + private static string GenerateRandomString(int length) + { + const string chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~"; + var random = RandomNumberGenerator.Create(); + var result = new char[length]; + + for (int i = 0; i < length; i++) + { + var randomNumber = new byte[4]; + random.GetBytes(randomNumber); + var index = (int)(BitConverter.ToUInt32(randomNumber, 0) % chars.Length); + result[i] = chars[index]; + } + + return new string(result); + } + + private static bool VerifyCodeChallenge(string codeVerifier, string codeChallenge, string method) + { + if (string.IsNullOrEmpty(codeVerifier)) return false; + + if (method == "S256") + { + using var sha256 = SHA256.Create(); + var hash = sha256.ComputeHash(Encoding.UTF8.GetBytes(codeVerifier)); + var base64 = Base64UrlEncoder.Encode(hash); + return string.Equals(base64, codeChallenge, StringComparison.Ordinal); + } + + if (method == "PLAIN") + { + return string.Equals(codeVerifier, codeChallenge, StringComparison.Ordinal); + } + + return false; + } +} \ No newline at end of file diff --git a/DysonNetwork.Pass/Auth/OpenId/AfdianOidcService.cs b/DysonNetwork.Pass/Auth/OpenId/AfdianOidcService.cs new file mode 100644 index 0000000..d55f090 --- /dev/null +++ b/DysonNetwork.Pass/Auth/OpenId/AfdianOidcService.cs @@ -0,0 +1,95 @@ +using System.Net.Http.Json; +using System.Text.Json; +using DysonNetwork.Pass; +using DysonNetwork.Shared.Cache; + +namespace DysonNetwork.Pass.Auth.OpenId; + +public class AfdianOidcService( + IConfiguration configuration, + IHttpClientFactory httpClientFactory, + AppDatabase db, + AuthService auth, + ICacheService cache, + ILogger logger +) + : OidcService(configuration, httpClientFactory, db, auth, cache) +{ + public override string ProviderName => "Afdian"; + protected override string DiscoveryEndpoint => ""; // Afdian doesn't have a standard OIDC discovery endpoint + protected override string ConfigSectionName => "Afdian"; + + public override string GetAuthorizationUrl(string state, string nonce) + { + var config = GetProviderConfig(); + var queryParams = new Dictionary + { + { "client_id", config.ClientId }, + { "redirect_uri", config.RedirectUri }, + { "response_type", "code" }, + { "scope", "basic" }, + { "state", state }, + }; + + var queryString = string.Join("&", queryParams.Select(p => $"{p.Key}={Uri.EscapeDataString(p.Value)}")); + return $"https://afdian.com/oauth2/authorize?{queryString}"; + } + + protected override Task GetDiscoveryDocumentAsync() + { + return Task.FromResult(new OidcDiscoveryDocument + { + AuthorizationEndpoint = "https://afdian.com/oauth2/authorize", + TokenEndpoint = "https://afdian.com/oauth2/access_token", + UserinfoEndpoint = null, + JwksUri = null + })!; + } + + public override async Task ProcessCallbackAsync(OidcCallbackData callbackData) + { + try + { + var config = GetProviderConfig(); + var content = new FormUrlEncodedContent(new Dictionary + { + { "client_id", config.ClientId }, + { "client_secret", config.ClientSecret }, + { "grant_type", "authorization_code" }, + { "code", callbackData.Code }, + { "redirect_uri", config.RedirectUri }, + }); + + var client = HttpClientFactory.CreateClient(); + var request = new HttpRequestMessage(HttpMethod.Post, "https://afdian.com/oauth2/access_token"); + request.Content = content; + + var response = await client.SendAsync(request); + response.EnsureSuccessStatusCode(); + + var json = await response.Content.ReadAsStringAsync(); + logger.LogInformation("Trying get userinfo from afdian, response: {Response}", json); + var afdianResponse = JsonDocument.Parse(json).RootElement; + + var user = afdianResponse.TryGetProperty("data", out var dataElement) ? dataElement : default; + var userId = user.TryGetProperty("user_id", out var userIdElement) ? userIdElement.GetString() ?? "" : ""; + var avatar = user.TryGetProperty("avatar", out var avatarElement) ? avatarElement.GetString() : null; + + return new OidcUserInfo + { + UserId = userId, + DisplayName = (user.TryGetProperty("name", out var nameElement) + ? nameElement.GetString() + : null) ?? "", + ProfilePictureUrl = avatar, + Provider = ProviderName + }; + } + catch (Exception ex) + { + // Due to afidan's API isn't compliant with OAuth2, we want more logs from it to investigate. + logger.LogError(ex, "Failed to get user info from Afdian"); + throw; + } + } +} \ No newline at end of file diff --git a/DysonNetwork.Pass/Auth/OpenId/AppleMobileSignInRequest.cs b/DysonNetwork.Pass/Auth/OpenId/AppleMobileSignInRequest.cs new file mode 100644 index 0000000..c655a88 --- /dev/null +++ b/DysonNetwork.Pass/Auth/OpenId/AppleMobileSignInRequest.cs @@ -0,0 +1,19 @@ + +using System.ComponentModel.DataAnnotations; +using System.Text.Json.Serialization; + +namespace DysonNetwork.Pass.Auth.OpenId; + +public class AppleMobileConnectRequest +{ + [Required] + public required string IdentityToken { get; set; } + [Required] + public required string AuthorizationCode { get; set; } +} + +public class AppleMobileSignInRequest : AppleMobileConnectRequest +{ + [Required] + public required string DeviceId { get; set; } +} diff --git a/DysonNetwork.Pass/Auth/OpenId/AppleOidcService.cs b/DysonNetwork.Pass/Auth/OpenId/AppleOidcService.cs new file mode 100644 index 0000000..67e4b21 --- /dev/null +++ b/DysonNetwork.Pass/Auth/OpenId/AppleOidcService.cs @@ -0,0 +1,280 @@ +using System.IdentityModel.Tokens.Jwt; +using System.Security.Cryptography; +using System.Text; +using System.Text.Json; +using System.Text.Json.Serialization; +using DysonNetwork.Pass; +using DysonNetwork.Shared.Cache; +using Microsoft.IdentityModel.Tokens; + +namespace DysonNetwork.Pass.Auth.OpenId; + +/// +/// Implementation of OpenID Connect service for Apple Sign In +/// +public class AppleOidcService( + IConfiguration configuration, + IHttpClientFactory httpClientFactory, + AppDatabase db, + AuthService auth, + ICacheService cache +) + : OidcService(configuration, httpClientFactory, db, auth, cache) +{ + private readonly IConfiguration _configuration = configuration; + private readonly IHttpClientFactory _httpClientFactory = httpClientFactory; + + public override string ProviderName => "apple"; + protected override string DiscoveryEndpoint => "https://appleid.apple.com/.well-known/openid-configuration"; + protected override string ConfigSectionName => "Apple"; + + public override string GetAuthorizationUrl(string state, string nonce) + { + var config = GetProviderConfig(); + + var queryParams = new Dictionary + { + { "client_id", config.ClientId }, + { "redirect_uri", config.RedirectUri }, + { "response_type", "code id_token" }, + { "scope", "name email" }, + { "response_mode", "form_post" }, + { "state", state }, + { "nonce", nonce } + }; + + var queryString = string.Join("&", queryParams.Select(p => $"{p.Key}={Uri.EscapeDataString(p.Value)}")); + return $"https://appleid.apple.com/auth/authorize?{queryString}"; + } + + public override async Task ProcessCallbackAsync(OidcCallbackData callbackData) + { + // Verify and decode the id_token + var userInfo = await ValidateTokenAsync(callbackData.IdToken); + + // If user data is provided in first login, parse it + if (!string.IsNullOrEmpty(callbackData.RawData)) + { + var userData = JsonSerializer.Deserialize(callbackData.RawData); + if (userData?.Name != null) + { + userInfo.FirstName = userData.Name.FirstName ?? ""; + userInfo.LastName = userData.Name.LastName ?? ""; + userInfo.DisplayName = $"{userInfo.FirstName} {userInfo.LastName}".Trim(); + } + } + + // Exchange authorization code for access token (optional, if you need the access token) + if (string.IsNullOrEmpty(callbackData.Code)) return userInfo; + var tokenResponse = await ExchangeCodeForTokensAsync(callbackData.Code); + if (tokenResponse == null) return userInfo; + userInfo.AccessToken = tokenResponse.AccessToken; + userInfo.RefreshToken = tokenResponse.RefreshToken; + + return userInfo; + } + + private async Task ValidateTokenAsync(string idToken) + { + // Get Apple's public keys + var jwksJson = await GetAppleJwksAsync(); + var jwks = JsonSerializer.Deserialize(jwksJson) ?? new AppleJwks { Keys = new List() }; + + // Parse the JWT header to get the key ID + var handler = new JwtSecurityTokenHandler(); + var jwtToken = handler.ReadJwtToken(idToken); + var kid = jwtToken.Header.Kid; + + // Find the matching key + var key = jwks.Keys.FirstOrDefault(k => k.Kid == kid); + if (key == null) + { + throw new SecurityTokenValidationException("Unable to find matching key in Apple's JWKS"); + } + + // Create the validation parameters + var validationParameters = new TokenValidationParameters + { + ValidateIssuer = true, + ValidIssuer = "https://appleid.apple.com", + ValidateAudience = true, + ValidAudience = GetProviderConfig().ClientId, + ValidateLifetime = true, + IssuerSigningKey = key.ToSecurityKey() + }; + + return ValidateAndExtractIdToken(idToken, validationParameters); + } + + protected override Dictionary BuildTokenRequestParameters( + string code, + ProviderConfiguration config, + string? codeVerifier + ) + { + var parameters = new Dictionary + { + { "client_id", config.ClientId }, + { "client_secret", GenerateClientSecret() }, + { "code", code }, + { "grant_type", "authorization_code" }, + { "redirect_uri", config.RedirectUri } + }; + + return parameters; + } + + private async Task GetAppleJwksAsync() + { + var client = _httpClientFactory.CreateClient(); + var response = await client.GetAsync("https://appleid.apple.com/auth/keys"); + response.EnsureSuccessStatusCode(); + + return await response.Content.ReadAsStringAsync(); + } + + /// + /// Generates a client secret for Apple Sign In using JWT + /// + private string GenerateClientSecret() + { + var now = DateTime.UtcNow; + var teamId = _configuration["Oidc:Apple:TeamId"]; + var clientId = _configuration["Oidc:Apple:ClientId"]; + var keyId = _configuration["Oidc:Apple:KeyId"]; + var privateKeyPath = _configuration["Oidc:Apple:PrivateKeyPath"]; + + if (string.IsNullOrEmpty(teamId) || string.IsNullOrEmpty(clientId) || string.IsNullOrEmpty(keyId) || + string.IsNullOrEmpty(privateKeyPath)) + { + throw new InvalidOperationException("Apple OIDC configuration is missing required values (TeamId, ClientId, KeyId, PrivateKeyPath)."); + } + + // Read the private key + var privateKey = File.ReadAllText(privateKeyPath); + + // Create the JWT header + var header = new Dictionary + { + { "alg", "ES256" }, + { "kid", keyId } + }; + + // Create the JWT payload + var payload = new Dictionary + { + { "iss", teamId }, + { "iat", ToUnixTimeSeconds(now) }, + { "exp", ToUnixTimeSeconds(now.AddMinutes(5)) }, + { "aud", "https://appleid.apple.com" }, + { "sub", clientId } + }; + + // Convert header and payload to Base64Url + var headerJson = JsonSerializer.Serialize(header); + var payloadJson = JsonSerializer.Serialize(payload); + var headerBase64 = Base64UrlEncode(Encoding.UTF8.GetBytes(headerJson)); + var payloadBase64 = Base64UrlEncode(Encoding.UTF8.GetBytes(payloadJson)); + + // Create the signature + var dataToSign = $"{headerBase64}.{payloadBase64}"; + var signature = SignWithECDsa(dataToSign, privateKey); + + // Combine all parts + return $"{headerBase64}.{payloadBase64}.{signature}"; + } + + private long ToUnixTimeSeconds(DateTime dateTime) + { + return new DateTimeOffset(dateTime).ToUnixTimeSeconds(); + } + + private string SignWithECDsa(string dataToSign, string privateKey) + { + using var ecdsa = ECDsa.Create(); + ecdsa.ImportFromPem(privateKey); + + var bytes = Encoding.UTF8.GetBytes(dataToSign); + var signature = ecdsa.SignData(bytes, HashAlgorithmName.SHA256); + + return Base64UrlEncode(signature); + } + + private string Base64UrlEncode(byte[] data) + { + return Convert.ToBase64String(data) + .Replace('+', '-') + .Replace('/', '_') + .TrimEnd('='); + } +} + +public class AppleUserData +{ + [JsonPropertyName("name")] public AppleNameData? Name { get; set; } + + [JsonPropertyName("email")] public string? Email { get; set; } +} + +public class AppleNameData +{ + [JsonPropertyName("firstName")] public string? FirstName { get; set; } + + [JsonPropertyName("lastName")] public string? LastName { get; set; } +} + +public class AppleJwks +{ + [JsonPropertyName("keys")] public List Keys { get; set; } = new List(); +} + +public class AppleKey +{ + [JsonPropertyName("kty")] public string? Kty { get; set; } + + [JsonPropertyName("kid")] public string? Kid { get; set; } + + [JsonPropertyName("use")] public string? Use { get; set; } + + [JsonPropertyName("alg")] public string? Alg { get; set; } + + [JsonPropertyName("n")] public string? N { get; set; } + + [JsonPropertyName("e")] public string? E { get; set; } + + public SecurityKey ToSecurityKey() + { + if (Kty != "RSA" || string.IsNullOrEmpty(N) || string.IsNullOrEmpty(E)) + { + throw new InvalidOperationException("Invalid key data"); + } + + var parameters = new RSAParameters + { + Modulus = Base64UrlDecode(N), + Exponent = Base64UrlDecode(E) + }; + + var rsa = RSA.Create(); + rsa.ImportParameters(parameters); + + return new RsaSecurityKey(rsa); + } + + private byte[] Base64UrlDecode(string input) + { + var output = input + .Replace('-', '+') + .Replace('_', '/'); + + switch (output.Length % 4) + { + case 0: break; + case 2: output += "=="; break; + case 3: output += "="; break; + default: throw new InvalidOperationException("Invalid base64url string"); + } + + return Convert.FromBase64String(output); + } +} \ No newline at end of file diff --git a/DysonNetwork.Pass/Auth/OpenId/ConnectionController.cs b/DysonNetwork.Pass/Auth/OpenId/ConnectionController.cs new file mode 100644 index 0000000..2f5dadb --- /dev/null +++ b/DysonNetwork.Pass/Auth/OpenId/ConnectionController.cs @@ -0,0 +1,409 @@ +using DysonNetwork.Pass.Account; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using DysonNetwork.Shared.Cache; +using NodaTime; + +namespace DysonNetwork.Pass.Auth.OpenId; + +[ApiController] +[Route("/api/accounts/me/connections")] +[Authorize] +public class ConnectionController( + AppDatabase db, + IEnumerable oidcServices, + AccountService accounts, + AuthService auth, + ICacheService cache +) : ControllerBase +{ + private const string StateCachePrefix = "oidc-state:"; + private const string ReturnUrlCachePrefix = "oidc-returning:"; + private static readonly TimeSpan StateExpiration = TimeSpan.FromMinutes(15); + + [HttpGet] + public async Task>> GetConnections() + { + if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) + return Unauthorized(); + + var connections = await db.AccountConnections + .Where(c => c.AccountId == currentUser.Id) + .Select(c => new + { + c.Id, + c.AccountId, + c.Provider, + c.ProvidedIdentifier, + c.Meta, + c.LastUsedAt, + c.CreatedAt, + c.UpdatedAt, + }) + .ToListAsync(); + return Ok(connections); + } + + [HttpDelete("{id:guid}")] + public async Task RemoveConnection(Guid id) + { + if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) + return Unauthorized(); + + var connection = await db.AccountConnections + .Where(c => c.Id == id && c.AccountId == currentUser.Id) + .FirstOrDefaultAsync(); + if (connection == null) + return NotFound(); + + db.AccountConnections.Remove(connection); + await db.SaveChangesAsync(); + + return Ok(); + } + + [HttpPost("/auth/connect/apple/mobile")] + public async Task ConnectAppleMobile([FromBody] AppleMobileConnectRequest request) + { + if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) + return Unauthorized(); + + if (GetOidcService("apple") is not AppleOidcService appleService) + return StatusCode(503, "Apple OIDC service not available"); + + var callbackData = new OidcCallbackData + { + IdToken = request.IdentityToken, + Code = request.AuthorizationCode, + }; + + OidcUserInfo userInfo; + try + { + userInfo = await appleService.ProcessCallbackAsync(callbackData); + } + catch (Exception ex) + { + return BadRequest($"Error processing Apple token: {ex.Message}"); + } + + var existingConnection = await db.AccountConnections + .FirstOrDefaultAsync(c => + c.Provider == "apple" && + c.ProvidedIdentifier == userInfo.UserId); + + if (existingConnection != null) + { + return BadRequest( + $"This Apple account is already linked to {(existingConnection.AccountId == currentUser.Id ? "your account" : "another user")}."); + } + + db.AccountConnections.Add(new AccountConnection + { + AccountId = currentUser.Id, + Provider = "apple", + ProvidedIdentifier = userInfo.UserId!, + AccessToken = userInfo.AccessToken, + RefreshToken = userInfo.RefreshToken, + LastUsedAt = SystemClock.Instance.GetCurrentInstant(), + Meta = userInfo.ToMetadata(), + }); + + await db.SaveChangesAsync(); + + return Ok(new { message = "Successfully connected Apple account." }); + } + + private OidcService? GetOidcService(string provider) + { + return oidcServices.FirstOrDefault(s => s.ProviderName.Equals(provider, StringComparison.OrdinalIgnoreCase)); + } + + public class ConnectProviderRequest + { + public string Provider { get; set; } = null!; + public string? ReturnUrl { get; set; } + } + + /// + /// Initiates manual connection to an OAuth provider for the current user + /// + [HttpPost("connect")] + public async Task> InitiateConnection([FromBody] ConnectProviderRequest request) + { + if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) + return Unauthorized(); + + var oidcService = GetOidcService(request.Provider); + if (oidcService == null) + return BadRequest($"Provider '{request.Provider}' is not supported"); + + var existingConnection = await db.AccountConnections + .AnyAsync(c => c.AccountId == currentUser.Id && c.Provider == oidcService.ProviderName); + + if (existingConnection) + return BadRequest($"You already have a {request.Provider} connection"); + + var state = Guid.NewGuid().ToString("N"); + var nonce = Guid.NewGuid().ToString("N"); + var stateValue = $"{currentUser.Id}|{request.Provider}|{nonce}"; + var finalReturnUrl = !string.IsNullOrEmpty(request.ReturnUrl) ? request.ReturnUrl : "/settings/connections"; + + // Store state and return URL in cache + await cache.SetAsync($"{StateCachePrefix}{state}", stateValue, StateExpiration); + await cache.SetAsync($"{ReturnUrlCachePrefix}{state}", finalReturnUrl, StateExpiration); + + var authUrl = oidcService.GetAuthorizationUrl(state, nonce); + + return Ok(new + { + authUrl, + message = $"Redirect to this URL to connect your {request.Provider} account" + }); + } + + [AllowAnonymous] + [Route("/api/auth/callback/{provider}")] + [HttpGet, HttpPost] + public async Task HandleCallback([FromRoute] string provider) + { + var oidcService = GetOidcService(provider); + if (oidcService == null) + return BadRequest($"Provider '{provider}' is not supported."); + + var callbackData = await ExtractCallbackData(Request); + if (callbackData.State == null) + return BadRequest("State parameter is missing."); + + // Get the state from the cache + var stateKey = $"{StateCachePrefix}{callbackData.State}"; + + // Try to get the state as OidcState first (new format) + var oidcState = await cache.GetAsync(stateKey); + + // If not found, try to get as string (legacy format) + if (oidcState == null) + { + var stateValue = await cache.GetAsync(stateKey); + if (string.IsNullOrEmpty(stateValue) || !OidcState.TryParse(stateValue, out oidcState) || oidcState == null) + return BadRequest("Invalid or expired state parameter"); + } + + // Remove the state from cache to prevent replay attacks + await cache.RemoveAsync(stateKey); + + // Handle the flow based on state type + if (oidcState.FlowType == OidcFlowType.Connect && oidcState.AccountId.HasValue) + { + // Connection flow + if (oidcState.DeviceId != null) + { + callbackData.State = oidcState.DeviceId; + } + return await HandleManualConnection(provider, oidcService, callbackData, oidcState.AccountId.Value); + } + else if (oidcState.FlowType == OidcFlowType.Login) + { + // Login/Registration flow + if (!string.IsNullOrEmpty(oidcState.DeviceId)) + { + callbackData.State = oidcState.DeviceId; + } + + // Store return URL if provided + if (!string.IsNullOrEmpty(oidcState.ReturnUrl) && oidcState.ReturnUrl != "/") + { + var returnUrlKey = $"{ReturnUrlCachePrefix}{callbackData.State}"; + await cache.SetAsync(returnUrlKey, oidcState.ReturnUrl, StateExpiration); + } + + return await HandleLoginOrRegistration(provider, oidcService, callbackData); + } + + return BadRequest("Unsupported flow type"); + } + + private async Task HandleManualConnection( + string provider, + OidcService oidcService, + OidcCallbackData callbackData, + Guid accountId + ) + { + provider = provider.ToLower(); + + OidcUserInfo userInfo; + try + { + userInfo = await oidcService.ProcessCallbackAsync(callbackData); + } + catch (Exception ex) + { + return BadRequest($"Error processing {provider} authentication: {ex.Message}"); + } + + if (string.IsNullOrEmpty(userInfo.UserId)) + { + return BadRequest($"{provider} did not return a valid user identifier."); + } + + // Extract device ID from the callback state if available + var deviceId = !string.IsNullOrEmpty(callbackData.State) ? callbackData.State : string.Empty; + + // Check if this provider account is already connected to any user + var existingConnection = await db.AccountConnections + .FirstOrDefaultAsync(c => + c.Provider == provider && + c.ProvidedIdentifier == userInfo.UserId); + + // If it's connected to a different user, return error + if (existingConnection != null && existingConnection.AccountId != accountId) + { + return BadRequest($"This {provider} account is already linked to another user."); + } + + // Check if the current user already has this provider connected + var userHasProvider = await db.AccountConnections + .AnyAsync(c => + c.AccountId == accountId && + c.Provider == provider); + + if (userHasProvider) + { + // Update existing connection with new tokens + var connection = await db.AccountConnections + .FirstOrDefaultAsync(c => + c.AccountId == accountId && + c.Provider == provider); + + if (connection != null) + { + connection.AccessToken = userInfo.AccessToken; + connection.RefreshToken = userInfo.RefreshToken; + connection.LastUsedAt = SystemClock.Instance.GetCurrentInstant(); + connection.Meta = userInfo.ToMetadata(); + } + } + else + { + // Create new connection + db.AccountConnections.Add(new AccountConnection + { + AccountId = accountId, + Provider = provider, + ProvidedIdentifier = userInfo.UserId!, + AccessToken = userInfo.AccessToken, + RefreshToken = userInfo.RefreshToken, + LastUsedAt = SystemClock.Instance.GetCurrentInstant(), + Meta = userInfo.ToMetadata(), + }); + } + + try + { + await db.SaveChangesAsync(); + } + catch (DbUpdateException) + { + return StatusCode(500, $"Failed to save {provider} connection. Please try again."); + } + + // Clean up and redirect + var returnUrlKey = $"{ReturnUrlCachePrefix}{callbackData.State}"; + var returnUrl = await cache.GetAsync(returnUrlKey); + await cache.RemoveAsync(returnUrlKey); + + return Redirect(string.IsNullOrEmpty(returnUrl) ? "/auth/callback" : returnUrl); + } + + private async Task HandleLoginOrRegistration( + string provider, + OidcService oidcService, + OidcCallbackData callbackData + ) + { + OidcUserInfo userInfo; + try + { + userInfo = await oidcService.ProcessCallbackAsync(callbackData); + } + catch (Exception ex) + { + return BadRequest($"Error processing callback: {ex.Message}"); + } + + if (string.IsNullOrEmpty(userInfo.Email) || string.IsNullOrEmpty(userInfo.UserId)) + { + return BadRequest($"Email or user ID is missing from {provider}'s response"); + } + + var connection = await db.AccountConnections + .Include(c => c.Account) + .FirstOrDefaultAsync(c => c.Provider == provider && c.ProvidedIdentifier == userInfo.UserId); + + var clock = SystemClock.Instance; + if (connection != null) + { + // Login existing user + var deviceId = !string.IsNullOrEmpty(callbackData.State) ? + callbackData.State.Split('|').FirstOrDefault() : + string.Empty; + + var challenge = await oidcService.CreateChallengeForUserAsync( + userInfo, + connection.Account, + HttpContext, + deviceId ?? string.Empty); + return Redirect($"/auth/callback?challenge={challenge.Id}"); + } + + // Register new user + var account = await accounts.LookupAccount(userInfo.Email) ?? await accounts.CreateAccount(userInfo); + + // Create connection for new or existing user + var newConnection = new AccountConnection + { + Account = account, + Provider = provider, + ProvidedIdentifier = userInfo.UserId!, + AccessToken = userInfo.AccessToken, + RefreshToken = userInfo.RefreshToken, + LastUsedAt = clock.GetCurrentInstant(), + Meta = userInfo.ToMetadata() + }; + db.AccountConnections.Add(newConnection); + + await db.SaveChangesAsync(); + + var loginSession = await auth.CreateSessionForOidcAsync(account, clock.GetCurrentInstant()); + var loginToken = auth.CreateToken(loginSession); + return Redirect($"/auth/token?token={loginToken}"); + } + + private static async Task ExtractCallbackData(HttpRequest request) + { + var data = new OidcCallbackData(); + switch (request.Method) + { + case "GET": + data.Code = Uri.UnescapeDataString(request.Query["code"].FirstOrDefault() ?? ""); + data.IdToken = Uri.UnescapeDataString(request.Query["id_token"].FirstOrDefault() ?? ""); + data.State = Uri.UnescapeDataString(request.Query["state"].FirstOrDefault() ?? ""); + break; + case "POST" when request.HasFormContentType: + { + var form = await request.ReadFormAsync(); + data.Code = Uri.UnescapeDataString(form["code"].FirstOrDefault() ?? ""); + data.IdToken = Uri.UnescapeDataString(form["id_token"].FirstOrDefault() ?? ""); + data.State = Uri.UnescapeDataString(form["state"].FirstOrDefault() ?? ""); + if (form.ContainsKey("user")) + data.RawData = Uri.UnescapeDataString(form["user"].FirstOrDefault() ?? ""); + + break; + } + } + + return data; + } +} \ No newline at end of file diff --git a/DysonNetwork.Pass/Auth/OpenId/DiscordOidcService.cs b/DysonNetwork.Pass/Auth/OpenId/DiscordOidcService.cs new file mode 100644 index 0000000..8d8cc20 --- /dev/null +++ b/DysonNetwork.Pass/Auth/OpenId/DiscordOidcService.cs @@ -0,0 +1,116 @@ +using System.Net.Http.Json; +using System.Text.Json; +using DysonNetwork.Pass; +using DysonNetwork.Shared.Cache; + +namespace DysonNetwork.Pass.Auth.OpenId; + +public class DiscordOidcService( + IConfiguration configuration, + IHttpClientFactory httpClientFactory, + AppDatabase db, + AuthService auth, + ICacheService cache +) + : OidcService(configuration, httpClientFactory, db, auth, cache) +{ + public override string ProviderName => "Discord"; + protected override string DiscoveryEndpoint => ""; // Discord doesn't have a standard OIDC discovery endpoint + protected override string ConfigSectionName => "Discord"; + + public override string GetAuthorizationUrl(string state, string nonce) + { + var config = GetProviderConfig(); + var queryParams = new Dictionary + { + { "client_id", config.ClientId }, + { "redirect_uri", config.RedirectUri }, + { "response_type", "code" }, + { "scope", "identify email" }, + { "state", state }, + }; + + var queryString = string.Join("&", queryParams.Select(p => $"{p.Key}={Uri.EscapeDataString(p.Value)}")); + return $"https://discord.com/oauth2/authorize?{queryString}"; + } + + protected override Task GetDiscoveryDocumentAsync() + { + return Task.FromResult(new OidcDiscoveryDocument + { + AuthorizationEndpoint = "https://discord.com/oauth2/authorize", + TokenEndpoint = "https://discord.com/oauth2/token", + UserinfoEndpoint = "https://discord.com/users/@me", + JwksUri = null + })!; + } + + public override async Task ProcessCallbackAsync(OidcCallbackData callbackData) + { + var tokenResponse = await ExchangeCodeForTokensAsync(callbackData.Code); + if (tokenResponse?.AccessToken == null) + { + throw new InvalidOperationException("Failed to obtain access token from Discord"); + } + + var userInfo = await GetUserInfoAsync(tokenResponse.AccessToken); + + userInfo.AccessToken = tokenResponse.AccessToken; + userInfo.RefreshToken = tokenResponse.RefreshToken; + + return userInfo; + } + + protected override async Task ExchangeCodeForTokensAsync(string code, + string? codeVerifier = null) + { + var config = GetProviderConfig(); + var client = HttpClientFactory.CreateClient(); + + var content = new FormUrlEncodedContent(new Dictionary + { + { "client_id", config.ClientId }, + { "client_secret", config.ClientSecret }, + { "grant_type", "authorization_code" }, + { "code", code }, + { "redirect_uri", config.RedirectUri }, + }); + + var response = await client.PostAsync("https://discord.com/oauth2/token", content); + response.EnsureSuccessStatusCode(); + + return await response.Content.ReadFromJsonAsync(); + } + + private async Task GetUserInfoAsync(string accessToken) + { + var client = HttpClientFactory.CreateClient(); + var request = new HttpRequestMessage(HttpMethod.Get, "https://discord.com/users/@me"); + request.Headers.Add("Authorization", $"Bearer {accessToken}"); + + var response = await client.SendAsync(request); + response.EnsureSuccessStatusCode(); + + var json = await response.Content.ReadAsStringAsync(); + var discordUser = JsonDocument.Parse(json).RootElement; + + var userId = discordUser.GetProperty("id").GetString() ?? ""; + var avatar = discordUser.TryGetProperty("avatar", out var avatarElement) ? avatarElement.GetString() : null; + + return new OidcUserInfo + { + UserId = userId, + Email = (discordUser.TryGetProperty("email", out var emailElement) ? emailElement.GetString() : null) ?? "", + EmailVerified = discordUser.TryGetProperty("verified", out var verifiedElement) && + verifiedElement.GetBoolean(), + DisplayName = (discordUser.TryGetProperty("global_name", out var globalNameElement) + ? globalNameElement.GetString() + : null) ?? "", + PreferredUsername = discordUser.GetProperty("username").GetString() ?? "", + ProfilePictureUrl = !string.IsNullOrEmpty(avatar) + ? $"https://cdn.discordapp.com/avatars/{userId}/{avatar}.png" + : "", + Provider = ProviderName + }; + } +} \ No newline at end of file diff --git a/DysonNetwork.Pass/Auth/OpenId/GitHubOidcService.cs b/DysonNetwork.Pass/Auth/OpenId/GitHubOidcService.cs new file mode 100644 index 0000000..0e36906 --- /dev/null +++ b/DysonNetwork.Pass/Auth/OpenId/GitHubOidcService.cs @@ -0,0 +1,128 @@ +using System.Net.Http.Json; +using System.Text.Json; +using DysonNetwork.Pass; +using DysonNetwork.Shared.Cache; + +namespace DysonNetwork.Pass.Auth.OpenId; + +public class GitHubOidcService( + IConfiguration configuration, + IHttpClientFactory httpClientFactory, + AppDatabase db, + AuthService auth, + ICacheService cache +) + : OidcService(configuration, httpClientFactory, db, auth, cache) +{ + public override string ProviderName => "GitHub"; + protected override string DiscoveryEndpoint => ""; // GitHub doesn't have a standard OIDC discovery endpoint + protected override string ConfigSectionName => "GitHub"; + + public override string GetAuthorizationUrl(string state, string nonce) + { + var config = GetProviderConfig(); + var queryParams = new Dictionary + { + { "client_id", config.ClientId }, + { "redirect_uri", config.RedirectUri }, + { "scope", "user:email" }, + { "state", state }, + }; + + var queryString = string.Join("&", queryParams.Select(p => $"{p.Key}={Uri.EscapeDataString(p.Value)}")); + return $"https://github.com/login/oauth/authorize?{queryString}"; + } + + public override async Task ProcessCallbackAsync(OidcCallbackData callbackData) + { + var tokenResponse = await ExchangeCodeForTokensAsync(callbackData.Code); + if (tokenResponse?.AccessToken == null) + { + throw new InvalidOperationException("Failed to obtain access token from GitHub"); + } + + var userInfo = await GetUserInfoAsync(tokenResponse.AccessToken); + + userInfo.AccessToken = tokenResponse.AccessToken; + userInfo.RefreshToken = tokenResponse.RefreshToken; + + return userInfo; + } + + protected override async Task ExchangeCodeForTokensAsync(string code, + string? codeVerifier = null) + { + var config = GetProviderConfig(); + var client = HttpClientFactory.CreateClient(); + + var tokenRequest = new HttpRequestMessage(HttpMethod.Post, "https://github.com/login/oauth/access_token") + { + Content = new FormUrlEncodedContent(new Dictionary + { + { "client_id", config.ClientId }, + { "client_secret", config.ClientSecret }, + { "code", code }, + { "redirect_uri", config.RedirectUri }, + }) + }; + tokenRequest.Headers.Add("Accept", "application/json"); + + var response = await client.SendAsync(tokenRequest); + response.EnsureSuccessStatusCode(); + + return await response.Content.ReadFromJsonAsync(); + } + + private async Task GetUserInfoAsync(string accessToken) + { + var client = HttpClientFactory.CreateClient(); + var request = new HttpRequestMessage(HttpMethod.Get, "https://api.github.com/user"); + request.Headers.Add("Authorization", $"Bearer {accessToken}"); + request.Headers.Add("User-Agent", "DysonNetwork.Pass"); + + var response = await client.SendAsync(request); + response.EnsureSuccessStatusCode(); + + var json = await response.Content.ReadAsStringAsync(); + var githubUser = JsonDocument.Parse(json).RootElement; + + var email = githubUser.TryGetProperty("email", out var emailElement) ? emailElement.GetString() : null; + if (string.IsNullOrEmpty(email)) + { + email = await GetPrimaryEmailAsync(accessToken); + } + + return new OidcUserInfo + { + UserId = githubUser.GetProperty("id").GetInt64().ToString(), + Email = email, + DisplayName = githubUser.TryGetProperty("name", out var nameElement) ? nameElement.GetString() ?? "" : "", + PreferredUsername = githubUser.GetProperty("login").GetString() ?? "", + ProfilePictureUrl = githubUser.TryGetProperty("avatar_url", out var avatarElement) + ? avatarElement.GetString() ?? "" + : "", + Provider = ProviderName + }; + } + + private async Task GetPrimaryEmailAsync(string accessToken) + { + var client = HttpClientFactory.CreateClient(); + var request = new HttpRequestMessage(HttpMethod.Get, "https://api.github.com/user/emails"); + request.Headers.Add("Authorization", $"Bearer {accessToken}"); + request.Headers.Add("User-Agent", "DysonNetwork.Pass"); + + var response = await client.SendAsync(request); + if (!response.IsSuccessStatusCode) return null; + + var emails = await response.Content.ReadFromJsonAsync>(); + return emails?.FirstOrDefault(e => e.Primary)?.Email; + } + + private class GitHubEmail + { + public string Email { get; set; } = ""; + public bool Primary { get; set; } + public bool Verified { get; set; } + } +} \ No newline at end of file diff --git a/DysonNetwork.Pass/Auth/OpenId/GoogleOidcService.cs b/DysonNetwork.Pass/Auth/OpenId/GoogleOidcService.cs new file mode 100644 index 0000000..5a1927c --- /dev/null +++ b/DysonNetwork.Pass/Auth/OpenId/GoogleOidcService.cs @@ -0,0 +1,137 @@ +using System.IdentityModel.Tokens.Jwt; +using System.Net.Http.Json; +using System.Security.Cryptography; +using System.Text; +using DysonNetwork.Pass; +using DysonNetwork.Shared.Cache; +using Microsoft.IdentityModel.Tokens; + +namespace DysonNetwork.Pass.Auth.OpenId; + +public class GoogleOidcService( + IConfiguration configuration, + IHttpClientFactory httpClientFactory, + AppDatabase db, + AuthService auth, + ICacheService cache +) + : OidcService(configuration, httpClientFactory, db, auth, cache) +{ + private readonly IHttpClientFactory _httpClientFactory = httpClientFactory; + + public override string ProviderName => "google"; + protected override string DiscoveryEndpoint => "https://accounts.google.com/.well-known/openid-configuration"; + protected override string ConfigSectionName => "Google"; + + public override string GetAuthorizationUrl(string state, string nonce) + { + var config = GetProviderConfig(); + var discoveryDocument = GetDiscoveryDocumentAsync().GetAwaiter().GetResult(); + + if (discoveryDocument?.AuthorizationEndpoint == null) + { + throw new InvalidOperationException("Authorization endpoint not found in discovery document"); + } + + var queryParams = new Dictionary + { + { "client_id", config.ClientId }, + { "redirect_uri", config.RedirectUri }, + { "response_type", "code" }, + { "scope", "openid email profile" }, + { "state", state }, // No '|codeVerifier' appended anymore + { "nonce", nonce } + }; + + var queryString = string.Join("&", queryParams.Select(p => $"{p.Key}={Uri.EscapeDataString(p.Value)}")); + return $"{discoveryDocument.AuthorizationEndpoint}?{queryString}"; + } + + public override async Task ProcessCallbackAsync(OidcCallbackData callbackData) + { + // No need to split or parse code verifier from state + var state = callbackData.State ?? ""; + callbackData.State = state; // Keep the original state if needed + + // Exchange the code for tokens + // Pass null or omit the parameter for codeVerifier as PKCE is removed + var tokenResponse = await ExchangeCodeForTokensAsync(callbackData.Code, null); + if (tokenResponse?.IdToken == null) + { + throw new InvalidOperationException("Failed to obtain ID token from Google"); + } + + // Validate the ID token + var userInfo = await ValidateTokenAsync(tokenResponse.IdToken); + + // Set tokens on the user info + userInfo.AccessToken = tokenResponse.AccessToken; + userInfo.RefreshToken = tokenResponse.RefreshToken; + + // Try to fetch additional profile data if userinfo endpoint is available + try + { + var discoveryDocument = await GetDiscoveryDocumentAsync(); + if (discoveryDocument?.UserinfoEndpoint != null && !string.IsNullOrEmpty(tokenResponse.AccessToken)) + { + var client = _httpClientFactory.CreateClient(); + client.DefaultRequestHeaders.Authorization = + new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", tokenResponse.AccessToken); + + var userInfoResponse = + await client.GetFromJsonAsync>(discoveryDocument.UserinfoEndpoint); + + if (userInfoResponse != null) + { + if (userInfoResponse.TryGetValue("picture", out var picture) && picture != null) + { + userInfo.ProfilePictureUrl = picture.ToString(); + } + } + } + } + catch + { + // Ignore errors when fetching additional profile data + } + + return userInfo; + } + + private async Task ValidateTokenAsync(string idToken) + { + var discoveryDocument = await GetDiscoveryDocumentAsync(); + if (discoveryDocument?.JwksUri == null) + { + throw new InvalidOperationException("JWKS URI not found in discovery document"); + } + + var client = _httpClientFactory.CreateClient(); + var jwksResponse = await client.GetFromJsonAsync(discoveryDocument.JwksUri); + if (jwksResponse == null) + { + throw new InvalidOperationException("Failed to retrieve JWKS from Google"); + } + + var handler = new JwtSecurityTokenHandler(); + var jwtToken = handler.ReadJwtToken(idToken); + var kid = jwtToken.Header.Kid; + var signingKey = jwksResponse.Keys.FirstOrDefault(k => k.Kid == kid); + if (signingKey == null) + { + throw new SecurityTokenValidationException("Unable to find matching key in Google's JWKS"); + } + + var validationParameters = new TokenValidationParameters + { + ValidateIssuer = true, + ValidIssuer = "https://accounts.google.com", + ValidateAudience = true, + ValidAudience = GetProviderConfig().ClientId, + ValidateLifetime = true, + IssuerSigningKey = signingKey + }; + + return ValidateAndExtractIdToken(idToken, validationParameters); + } +} \ No newline at end of file diff --git a/DysonNetwork.Pass/Auth/OpenId/MicrosoftOidcService.cs b/DysonNetwork.Pass/Auth/OpenId/MicrosoftOidcService.cs new file mode 100644 index 0000000..0d1b4b8 --- /dev/null +++ b/DysonNetwork.Pass/Auth/OpenId/MicrosoftOidcService.cs @@ -0,0 +1,123 @@ +using System.Text.Json; +using DysonNetwork.Shared.Cache; + +namespace DysonNetwork.Pass.Auth.OpenId; + +public class MicrosoftOidcService( + IConfiguration configuration, + IHttpClientFactory httpClientFactory, + AppDatabase db, + AuthService auth, + ICacheService cache +) + : OidcService(configuration, httpClientFactory, db, auth, cache) +{ + public override string ProviderName => "Microsoft"; + + protected override string DiscoveryEndpoint => Configuration[$"Oidc:{ConfigSectionName}:DiscoveryEndpoint"] ?? + throw new InvalidOperationException( + "Microsoft OIDC discovery endpoint is not configured."); + + protected override string ConfigSectionName => "Microsoft"; + + public override string GetAuthorizationUrl(string state, string nonce) + { + var config = GetProviderConfig(); + var discoveryDocument = GetDiscoveryDocumentAsync().GetAwaiter().GetResult(); + if (discoveryDocument?.AuthorizationEndpoint == null) + throw new InvalidOperationException("Authorization endpoint not found in discovery document."); + + var queryParams = new Dictionary + { + { "client_id", config.ClientId }, + { "response_type", "code" }, + { "redirect_uri", config.RedirectUri }, + { "response_mode", "query" }, + { "scope", "openid profile email" }, + { "state", state }, + { "nonce", nonce }, + }; + + var queryString = string.Join("&", queryParams.Select(p => $"{p.Key}={Uri.EscapeDataString(p.Value)}")); + return $"{discoveryDocument.AuthorizationEndpoint}?{queryString}"; + } + + public override async Task ProcessCallbackAsync(OidcCallbackData callbackData) + { + var tokenResponse = await ExchangeCodeForTokensAsync(callbackData.Code); + if (tokenResponse?.AccessToken == null) + { + throw new InvalidOperationException("Failed to obtain access token from Microsoft"); + } + + var userInfo = await GetUserInfoAsync(tokenResponse.AccessToken); + + userInfo.AccessToken = tokenResponse.AccessToken; + userInfo.RefreshToken = tokenResponse.RefreshToken; + + return userInfo; + } + + protected override async Task ExchangeCodeForTokensAsync(string code, + string? codeVerifier = null) + { + var config = GetProviderConfig(); + var discoveryDocument = await GetDiscoveryDocumentAsync(); + if (discoveryDocument?.TokenEndpoint == null) + { + throw new InvalidOperationException("Token endpoint not found in discovery document."); + } + + var client = HttpClientFactory.CreateClient(); + + var tokenRequest = new HttpRequestMessage(HttpMethod.Post, discoveryDocument.TokenEndpoint) + { + Content = new FormUrlEncodedContent(new Dictionary + { + { "client_id", config.ClientId }, + { "scope", "openid profile email" }, + { "code", code }, + { "redirect_uri", config.RedirectUri }, + { "grant_type", "authorization_code" }, + { "client_secret", config.ClientSecret }, + }) + }; + + var response = await client.SendAsync(tokenRequest); + response.EnsureSuccessStatusCode(); + + return await response.Content.ReadFromJsonAsync(); + } + + private async Task GetUserInfoAsync(string accessToken) + { + var discoveryDocument = await GetDiscoveryDocumentAsync(); + if (discoveryDocument?.UserinfoEndpoint == null) + throw new InvalidOperationException("Userinfo endpoint not found in discovery document."); + + var client = HttpClientFactory.CreateClient(); + var request = new HttpRequestMessage(HttpMethod.Get, discoveryDocument.UserinfoEndpoint); + request.Headers.Add("Authorization", $"Bearer {accessToken}"); + + var response = await client.SendAsync(request); + response.EnsureSuccessStatusCode(); + + var json = await response.Content.ReadAsStringAsync(); + var microsoftUser = JsonDocument.Parse(json).RootElement; + + return new OidcUserInfo + { + UserId = microsoftUser.GetProperty("sub").GetString() ?? "", + Email = microsoftUser.TryGetProperty("email", out var emailElement) ? emailElement.GetString() : null, + DisplayName = + microsoftUser.TryGetProperty("name", out var nameElement) ? nameElement.GetString() ?? "" : "", + PreferredUsername = microsoftUser.TryGetProperty("preferred_username", out var preferredUsernameElement) + ? preferredUsernameElement.GetString() ?? "" + : "", + ProfilePictureUrl = microsoftUser.TryGetProperty("picture", out var pictureElement) + ? pictureElement.GetString() ?? "" + : "", + Provider = ProviderName + }; + } +} \ No newline at end of file diff --git a/DysonNetwork.Pass/Auth/OpenId/OidcController.cs b/DysonNetwork.Pass/Auth/OpenId/OidcController.cs new file mode 100644 index 0000000..43f5053 --- /dev/null +++ b/DysonNetwork.Pass/Auth/OpenId/OidcController.cs @@ -0,0 +1,194 @@ +using DysonNetwork.Pass.Account; +using DysonNetwork.Shared.Cache; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Microsoft.IdentityModel.Tokens; +using NodaTime; + +namespace DysonNetwork.Pass.Auth.OpenId; + +[ApiController] +[Route("/api/auth/login")] +public class OidcController( + IServiceProvider serviceProvider, + AppDatabase db, + AccountService accounts, + ICacheService cache +) + : ControllerBase +{ + private const string StateCachePrefix = "oidc-state:"; + private static readonly TimeSpan StateExpiration = TimeSpan.FromMinutes(15); + + [HttpGet("{provider}")] + public async Task OidcLogin( + [FromRoute] string provider, + [FromQuery] string? returnUrl = "/", + [FromHeader(Name = "X-Device-Id")] string? deviceId = null + ) + { + try + { + var oidcService = GetOidcService(provider); + + // If the user is already authenticated, treat as an account connection request + if (HttpContext.Items["CurrentUser"] is Account.Account currentUser) + { + var state = Guid.NewGuid().ToString(); + var nonce = Guid.NewGuid().ToString(); + + // Create and store connection state + var oidcState = OidcState.ForConnection(currentUser.Id, provider, nonce, deviceId); + await cache.SetAsync($"{StateCachePrefix}{state}", oidcState, StateExpiration); + + // The state parameter sent to the provider is the GUID key for the cache. + var authUrl = oidcService.GetAuthorizationUrl(state, nonce); + return Redirect(authUrl); + } + else // Otherwise, proceed with the login / registration flow + { + var nonce = Guid.NewGuid().ToString(); + var state = Guid.NewGuid().ToString(); + + // Create login state with return URL and device ID + var oidcState = OidcState.ForLogin(returnUrl ?? "/", deviceId); + await cache.SetAsync($"{StateCachePrefix}{state}", oidcState, StateExpiration); + var authUrl = oidcService.GetAuthorizationUrl(state, nonce); + return Redirect(authUrl); + } + } + catch (Exception ex) + { + return BadRequest($"Error initiating OpenID Connect flow: {ex.Message}"); + } + } + + /// + /// Mobile Apple Sign In endpoint + /// Handles Apple authentication directly from mobile apps + /// + [HttpPost("apple/mobile")] + public async Task> AppleMobileLogin( + [FromBody] AppleMobileSignInRequest request) + { + try + { + // Get Apple OIDC service + if (GetOidcService("apple") is not AppleOidcService appleService) + return StatusCode(503, "Apple OIDC service not available"); + + // Prepare callback data for processing + var callbackData = new OidcCallbackData + { + IdToken = request.IdentityToken, + Code = request.AuthorizationCode, + }; + + // Process the authentication + var userInfo = await appleService.ProcessCallbackAsync(callbackData); + + // Find or create user account using existing logic + var account = await FindOrCreateAccount(userInfo, "apple"); + + // Create session using the OIDC service + var challenge = await appleService.CreateChallengeForUserAsync( + userInfo, + account, + HttpContext, + request.DeviceId + ); + + return Ok(challenge); + } + catch (SecurityTokenValidationException ex) + { + return Unauthorized($"Invalid identity token: {ex.Message}"); + } + catch (Exception ex) + { + // Log the error + return StatusCode(500, $"Authentication failed: {ex.Message}"); + } + } + + private OidcService GetOidcService(string provider) + { + return provider.ToLower() switch + { + "apple" => serviceProvider.GetRequiredService(), + "google" => serviceProvider.GetRequiredService(), + "microsoft" => serviceProvider.GetRequiredService(), + "discord" => serviceProvider.GetRequiredService(), + "github" => serviceProvider.GetRequiredService(), + "afdian" => serviceProvider.GetRequiredService(), + _ => throw new ArgumentException($"Unsupported provider: {provider}") + }; + } + + private async Task FindOrCreateAccount(OidcUserInfo userInfo, string provider) + { + if (string.IsNullOrEmpty(userInfo.Email)) + throw new ArgumentException("Email is required for account creation"); + + // Check if an account exists by email + var existingAccount = await accounts.LookupAccount(userInfo.Email); + if (existingAccount != null) + { + // Check if this provider connection already exists + var existingConnection = await db.AccountConnections + .FirstOrDefaultAsync(c => c.AccountId == existingAccount.Id && + c.Provider == provider && + c.ProvidedIdentifier == userInfo.UserId); + + // If no connection exists, create one + if (existingConnection != null) + { + await db.AccountConnections + .Where(c => c.AccountId == existingAccount.Id && + c.Provider == provider && + c.ProvidedIdentifier == userInfo.UserId) + .ExecuteUpdateAsync(s => s + .SetProperty(c => c.LastUsedAt, SystemClock.Instance.GetCurrentInstant()) + .SetProperty(c => c.Meta, userInfo.ToMetadata())); + + return existingAccount; + } + + var connection = new AccountConnection + { + AccountId = existingAccount.Id, + Provider = provider, + ProvidedIdentifier = userInfo.UserId!, + AccessToken = userInfo.AccessToken, + RefreshToken = userInfo.RefreshToken, + LastUsedAt = SystemClock.Instance.GetCurrentInstant(), + Meta = userInfo.ToMetadata() + }; + + await db.AccountConnections.AddAsync(connection); + await db.SaveChangesAsync(); + + return existingAccount; + } + + // Create new account using the AccountService + var newAccount = await accounts.CreateAccount(userInfo); + + // Create the provider connection + var newConnection = new AccountConnection + { + AccountId = newAccount.Id, + Provider = provider, + ProvidedIdentifier = userInfo.UserId!, + AccessToken = userInfo.AccessToken, + RefreshToken = userInfo.RefreshToken, + LastUsedAt = SystemClock.Instance.GetCurrentInstant(), + Meta = userInfo.ToMetadata() + }; + + db.AccountConnections.Add(newConnection); + await db.SaveChangesAsync(); + + return newAccount; + } +} \ No newline at end of file diff --git a/DysonNetwork.Pass/Auth/OpenId/OidcService.cs b/DysonNetwork.Pass/Auth/OpenId/OidcService.cs new file mode 100644 index 0000000..ea1b7df --- /dev/null +++ b/DysonNetwork.Pass/Auth/OpenId/OidcService.cs @@ -0,0 +1,294 @@ +using System.IdentityModel.Tokens.Jwt; +using System.Text.Json.Serialization; +using DysonNetwork.Pass.Account; +using DysonNetwork.Shared.Cache; +using Microsoft.EntityFrameworkCore; +using Microsoft.IdentityModel.Tokens; +using NodaTime; + +namespace DysonNetwork.Pass.Auth.OpenId; + +/// +/// Base service for OpenID Connect authentication providers +/// +public abstract class OidcService( + IConfiguration configuration, + IHttpClientFactory httpClientFactory, + AppDatabase db, + AuthService auth, + ICacheService cache +) +{ + protected readonly IConfiguration Configuration = configuration; + protected readonly IHttpClientFactory HttpClientFactory = httpClientFactory; + protected readonly AppDatabase Db = db; + + /// + /// Gets the unique identifier for this provider + /// + public abstract string ProviderName { get; } + + /// + /// Gets the OIDC discovery document endpoint + /// + protected abstract string DiscoveryEndpoint { get; } + + /// + /// Gets configuration section name for this provider + /// + protected abstract string ConfigSectionName { get; } + + /// + /// Gets the authorization URL for initiating the authentication flow + /// + public abstract string GetAuthorizationUrl(string state, string nonce); + + /// + /// Process the callback from the OIDC provider + /// + public abstract Task ProcessCallbackAsync(OidcCallbackData callbackData); + + /// + /// Gets the provider configuration + /// + protected ProviderConfiguration GetProviderConfig() + { + return new ProviderConfiguration + { + ClientId = Configuration[$"Oidc:{ConfigSectionName}:ClientId"] ?? "", + ClientSecret = Configuration[$"Oidc:{ConfigSectionName}:ClientSecret"] ?? "", + RedirectUri = Configuration["BaseUrl"] + "/auth/callback/" + ProviderName.ToLower() + }; + } + + /// + /// Retrieves the OpenID Connect discovery document + /// + protected virtual async Task GetDiscoveryDocumentAsync() + { + // Construct a cache key unique to the current provider: + var cacheKey = $"oidc-discovery:{ProviderName}"; + + // Try getting the discovery document from cache first: + var (found, cachedDoc) = await cache.GetAsyncWithStatus(cacheKey); + if (found && cachedDoc != null) + { + return cachedDoc; + } + + // If it's not cached, fetch from the actual discovery endpoint: + var client = HttpClientFactory.CreateClient(); + var response = await client.GetAsync(DiscoveryEndpoint); + response.EnsureSuccessStatusCode(); + var doc = await response.Content.ReadFromJsonAsync(); + + // Store the discovery document in the cache for a while (e.g., 15 minutes): + if (doc is not null) + await cache.SetAsync(cacheKey, doc, TimeSpan.FromMinutes(15)); + + return doc; + + } + + /// + /// Exchange the authorization code for tokens + /// + protected virtual async Task ExchangeCodeForTokensAsync(string code, + string? codeVerifier = null) + { + var config = GetProviderConfig(); + var discoveryDocument = await GetDiscoveryDocumentAsync(); + + if (discoveryDocument?.TokenEndpoint == null) + { + throw new InvalidOperationException("Token endpoint not found in discovery document"); + } + + var client = HttpClientFactory.CreateClient(); + var content = new FormUrlEncodedContent(BuildTokenRequestParameters(code, config, codeVerifier)); + + var response = await client.PostAsync(discoveryDocument.TokenEndpoint, content); + response.EnsureSuccessStatusCode(); + + return await response.Content.ReadFromJsonAsync(); + } + + /// + /// Build the token request parameters + /// + protected virtual Dictionary BuildTokenRequestParameters(string code, ProviderConfiguration config, + string? codeVerifier) + { + var parameters = new Dictionary + { + { "client_id", config.ClientId }, + { "code", code }, + { "grant_type", "authorization_code" }, + { "redirect_uri", config.RedirectUri } + }; + + if (!string.IsNullOrEmpty(config.ClientSecret)) + { + parameters.Add("client_secret", config.ClientSecret); + } + + if (!string.IsNullOrEmpty(codeVerifier)) + { + parameters.Add("code_verifier", codeVerifier); + } + + return parameters; + } + + /// + /// Validates and extracts information from an ID token + /// + protected virtual OidcUserInfo ValidateAndExtractIdToken(string idToken, + TokenValidationParameters validationParameters) + { + var handler = new JwtSecurityTokenHandler(); + handler.ValidateToken(idToken, validationParameters, out _); + + var jwtToken = handler.ReadJwtToken(idToken); + + // Extract standard claims + var userId = jwtToken.Claims.FirstOrDefault(c => c.Type == "sub")?.Value; + var email = jwtToken.Claims.FirstOrDefault(c => c.Type == "email")?.Value; + var emailVerified = jwtToken.Claims.FirstOrDefault(c => c.Type == "email_verified")?.Value == "true"; + var name = jwtToken.Claims.FirstOrDefault(c => c.Type == "name")?.Value; + var givenName = jwtToken.Claims.FirstOrDefault(c => c.Type == "given_name")?.Value; + var familyName = jwtToken.Claims.FirstOrDefault(c => c.Type == "family_name")?.Value; + var preferredUsername = jwtToken.Claims.FirstOrDefault(c => c.Type == "preferred_username")?.Value; + var picture = jwtToken.Claims.FirstOrDefault(c => c.Type == "picture")?.Value; + + // Determine preferred username - try different options + var username = preferredUsername; + if (string.IsNullOrEmpty(username)) + { + // Fall back to email local part if no preferred username + username = !string.IsNullOrEmpty(email) ? email.Split('@')[0] : null; + } + + return new OidcUserInfo + { + UserId = userId, + Email = email, + EmailVerified = emailVerified, + FirstName = givenName ?? "", + LastName = familyName ?? "", + DisplayName = name ?? $"{givenName} {familyName}".Trim(), + PreferredUsername = username ?? "", + ProfilePictureUrl = picture, + Provider = ProviderName + }; + } + + /// + /// Creates a challenge and session for an authenticated user + /// Also creates or updates the account connection + /// + public async Task CreateChallengeForUserAsync( + OidcUserInfo userInfo, + Account.Account account, + HttpContext request, + string deviceId + ) + { + // Create or update the account connection + var connection = await Db.AccountConnections + .FirstOrDefaultAsync(c => c.Provider == ProviderName && + c.ProvidedIdentifier == userInfo.UserId && + c.AccountId == account.Id + ); + + if (connection is null) + { + connection = new AccountConnection + { + Provider = ProviderName, + ProvidedIdentifier = userInfo.UserId ?? "", + AccessToken = userInfo.AccessToken, + RefreshToken = userInfo.RefreshToken, + LastUsedAt = SystemClock.Instance.GetCurrentInstant(), + AccountId = account.Id + }; + await Db.AccountConnections.AddAsync(connection); + } + + // Create a challenge that's already completed + var now = SystemClock.Instance.GetCurrentInstant(); + var challenge = new Challenge + { + ExpiredAt = now.Plus(Duration.FromHours(1)), + StepTotal = await auth.DetectChallengeRisk(request.Request, account), + Type = ChallengeType.Oidc, + Platform = ChallengePlatform.Unidentified, + Audiences = [ProviderName], + Scopes = ["*"], + AccountId = account.Id, + DeviceId = deviceId, + IpAddress = request.Connection.RemoteIpAddress?.ToString() ?? null, + UserAgent = request.Request.Headers.UserAgent, + }; + challenge.StepRemain--; + if (challenge.StepRemain < 0) challenge.StepRemain = 0; + + await Db.AuthChallenges.AddAsync(challenge); + await Db.SaveChangesAsync(); + + return challenge; + } +} + +/// +/// Provider configuration from app settings +/// +public class ProviderConfiguration +{ + public string ClientId { get; set; } = ""; + public string ClientSecret { get; set; } = ""; + public string RedirectUri { get; set; } = ""; +} + +/// +/// OIDC Discovery Document +/// +public class OidcDiscoveryDocument +{ + [JsonPropertyName("authorization_endpoint")] + public string? AuthorizationEndpoint { get; set; } + + [JsonPropertyName("token_endpoint")] public string? TokenEndpoint { get; set; } + + [JsonPropertyName("userinfo_endpoint")] + public string? UserinfoEndpoint { get; set; } + + [JsonPropertyName("jwks_uri")] public string? JwksUri { get; set; } +} + +/// +/// Response from the token endpoint +/// +public class OidcTokenResponse +{ + [JsonPropertyName("access_token")] public string? AccessToken { get; set; } + + [JsonPropertyName("token_type")] public string? TokenType { get; set; } + + [JsonPropertyName("expires_in")] public int ExpiresIn { get; set; } + + [JsonPropertyName("refresh_token")] public string? RefreshToken { get; set; } + + [JsonPropertyName("id_token")] public string? IdToken { get; set; } +} + +/// +/// Data received in the callback from an OIDC provider +/// +public class OidcCallbackData +{ + public string Code { get; set; } = ""; + public string IdToken { get; set; } = ""; + public string? State { get; set; } + public string? RawData { get; set; } +} \ No newline at end of file diff --git a/DysonNetwork.Pass/Auth/OpenId/OidcState.cs b/DysonNetwork.Pass/Auth/OpenId/OidcState.cs new file mode 100644 index 0000000..4555a5e --- /dev/null +++ b/DysonNetwork.Pass/Auth/OpenId/OidcState.cs @@ -0,0 +1,189 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace DysonNetwork.Pass.Auth.OpenId; + +/// +/// Represents the state parameter used in OpenID Connect flows. +/// Handles serialization and deserialization of the state parameter. +/// +public class OidcState +{ + /// + /// The type of OIDC flow (login or connect). + /// + public OidcFlowType FlowType { get; set; } + + /// + /// The account ID (for connect flow). + /// + public Guid? AccountId { get; set; } + + + /// + /// The OIDC provider name. + /// + public string? Provider { get; set; } + + + /// + /// The nonce for CSRF protection. + /// + public string? Nonce { get; set; } + + + /// + /// The device ID for the authentication request. + /// + public string? DeviceId { get; set; } + + + /// + /// The return URL after authentication (for login flow). + /// + public string? ReturnUrl { get; set; } + + + /// + /// Creates a new OidcState for a connection flow. + /// + public static OidcState ForConnection(Guid accountId, string provider, string nonce, string? deviceId = null) + { + return new OidcState + { + FlowType = OidcFlowType.Connect, + AccountId = accountId, + Provider = provider, + Nonce = nonce, + DeviceId = deviceId + }; + } + + /// + /// Creates a new OidcState for a login flow. + /// + public static OidcState ForLogin(string returnUrl = "/", string? deviceId = null) + { + return new OidcState + { + FlowType = OidcFlowType.Login, + ReturnUrl = returnUrl, + DeviceId = deviceId + }; + } + + /// + /// The version of the state format. + /// + public int Version { get; set; } = 1; + + /// + /// Serializes the state to a JSON string for use in OIDC flows. + /// + public string Serialize() + { + return JsonSerializer.Serialize(this, new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull + }); + } + + /// + /// Attempts to parse a state string into an OidcState object. + /// + public static bool TryParse(string? stateString, out OidcState? state) + { + state = null; + + if (string.IsNullOrEmpty(stateString)) + return false; + + try + { + // First try to parse as JSON + try + { + state = JsonSerializer.Deserialize(stateString); + return state != null; + } + catch (JsonException) + { + // Not a JSON string, try legacy format for backward compatibility + return TryParseLegacyFormat(stateString, out state); + } + } + catch + { + return false; + } + } + + private static bool TryParseLegacyFormat(string stateString, out OidcState? state) + { + state = null; + var parts = stateString.Split('|'); + + // Check for connection flow format: {accountId}|{provider}|{nonce}|{deviceId}|connect + if (parts.Length >= 5 && + Guid.TryParse(parts[0], out var accountId) && + string.Equals(parts[^1], "connect", StringComparison.OrdinalIgnoreCase)) + { + state = new OidcState + { + FlowType = OidcFlowType.Connect, + AccountId = accountId, + Provider = parts[1], + Nonce = parts[2], + DeviceId = parts.Length >= 4 && !string.IsNullOrEmpty(parts[3]) ? parts[3] : null + }; + return true; + } + + // Check for login flow format: {returnUrl}|{deviceId}|login + if (parts.Length >= 2 && + parts.Length <= 3 && + (parts.Length < 3 || string.Equals(parts[^1], "login", StringComparison.OrdinalIgnoreCase))) + { + state = new OidcState + { + FlowType = OidcFlowType.Login, + ReturnUrl = parts[0], + DeviceId = parts.Length >= 2 && !string.IsNullOrEmpty(parts[1]) ? parts[1] : null + }; + return true; + } + + // Legacy format support (for backward compatibility) + if (parts.Length == 1) + { + state = new OidcState + { + FlowType = OidcFlowType.Login, + ReturnUrl = parts[0], + DeviceId = null + }; + return true; + } + + + return false; + } +} + +/// +/// Represents the type of OIDC flow. +/// +public enum OidcFlowType +{ + /// + /// Login or registration flow. + /// + Login, + + + /// + /// Account connection flow. + /// + Connect +} \ No newline at end of file diff --git a/DysonNetwork.Pass/Auth/OpenId/OidcUserInfo.cs b/DysonNetwork.Pass/Auth/OpenId/OidcUserInfo.cs new file mode 100644 index 0000000..973d76d --- /dev/null +++ b/DysonNetwork.Pass/Auth/OpenId/OidcUserInfo.cs @@ -0,0 +1,49 @@ +namespace DysonNetwork.Pass.Auth.OpenId; + +/// +/// Represents the user information from an OIDC provider +/// +public class OidcUserInfo +{ + public string? UserId { get; set; } + public string? Email { get; set; } + public bool EmailVerified { get; set; } + public string FirstName { get; set; } = ""; + public string LastName { get; set; } = ""; + public string DisplayName { get; set; } = ""; + public string PreferredUsername { get; set; } = ""; + public string? ProfilePictureUrl { get; set; } + public string Provider { get; set; } = ""; + public string? RefreshToken { get; set; } + public string? AccessToken { get; set; } + + public Dictionary ToMetadata() + { + var metadata = new Dictionary(); + + if (!string.IsNullOrWhiteSpace(UserId)) + metadata["user_id"] = UserId; + + if (!string.IsNullOrWhiteSpace(Email)) + metadata["email"] = Email; + + metadata["email_verified"] = EmailVerified; + + if (!string.IsNullOrWhiteSpace(FirstName)) + metadata["first_name"] = FirstName; + + if (!string.IsNullOrWhiteSpace(LastName)) + metadata["last_name"] = LastName; + + if (!string.IsNullOrWhiteSpace(DisplayName)) + metadata["display_name"] = DisplayName; + + if (!string.IsNullOrWhiteSpace(PreferredUsername)) + metadata["preferred_username"] = PreferredUsername; + + if (!string.IsNullOrWhiteSpace(ProfilePictureUrl)) + metadata["profile_picture_url"] = ProfilePictureUrl; + + return metadata; + } +} \ No newline at end of file diff --git a/DysonNetwork.Pass/Auth/Session.cs b/DysonNetwork.Pass/Auth/Session.cs new file mode 100644 index 0000000..4fcfee9 --- /dev/null +++ b/DysonNetwork.Pass/Auth/Session.cs @@ -0,0 +1,69 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Text.Json.Serialization; +using DysonNetwork.Pass; +using NodaTime; +using Point = NetTopologySuite.Geometries.Point; + +namespace DysonNetwork.Pass.Auth; + +public class Session : ModelBase +{ + public Guid Id { get; set; } = Guid.NewGuid(); + [MaxLength(1024)] public string? Label { get; set; } + public Instant? LastGrantedAt { get; set; } + public Instant? ExpiredAt { get; set; } + + public Guid AccountId { get; set; } + [JsonIgnore] public Account.Account Account { get; set; } = null!; + public Guid ChallengeId { get; set; } + public Challenge Challenge { get; set; } = null!; + public Guid? AppId { get; set; } + // public CustomApp? App { get; set; } +} + +public enum ChallengeType +{ + Login, + OAuth, // Trying to authorize other platforms + Oidc // Trying to connect other platforms +} + +public enum ChallengePlatform +{ + Unidentified, + Web, + Ios, + Android, + MacOs, + Windows, + Linux +} + +public class Challenge : ModelBase +{ + public Guid Id { get; set; } = Guid.NewGuid(); + public Instant? ExpiredAt { get; set; } + public int StepRemain { get; set; } + public int StepTotal { get; set; } + public int FailedAttempts { get; set; } + public ChallengePlatform Platform { get; set; } = ChallengePlatform.Unidentified; + 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(); + [MaxLength(128)] public string? IpAddress { get; set; } + [MaxLength(512)] public string? UserAgent { get; set; } + [MaxLength(256)] public string? DeviceId { get; set; } + [MaxLength(1024)] public string? Nonce { get; set; } + public Point? Location { get; set; } + + public Guid AccountId { get; set; } + [JsonIgnore] public Account.Account Account { get; set; } = null!; + + public Challenge Normalize() + { + if (StepRemain == 0 && BlacklistFactors.Count == 0) StepRemain = StepTotal; + return this; + } +} \ No newline at end of file diff --git a/DysonNetwork.Pass/Dockerfile b/DysonNetwork.Pass/Dockerfile new file mode 100644 index 0000000..5c35aff --- /dev/null +++ b/DysonNetwork.Pass/Dockerfile @@ -0,0 +1,23 @@ +FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base +USER $APP_UID +WORKDIR /app +EXPOSE 8080 +EXPOSE 8081 + +FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build +ARG BUILD_CONFIGURATION=Release +WORKDIR /src +COPY ["DysonNetwork.Pass/DysonNetwork.Pass.csproj", "DysonNetwork.Pass/"] +RUN dotnet restore "DysonNetwork.Pass/DysonNetwork.Pass.csproj" +COPY . . +WORKDIR "/src/DysonNetwork.Pass" +RUN dotnet build "./DysonNetwork.Pass.csproj" -c $BUILD_CONFIGURATION -o /app/build + +FROM build AS publish +ARG BUILD_CONFIGURATION=Release +RUN dotnet publish "./DysonNetwork.Pass.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false + +FROM base AS final +WORKDIR /app +COPY --from=publish /app/publish . +ENTRYPOINT ["dotnet", "DysonNetwork.Pass.dll"] diff --git a/DysonNetwork.Pass/DysonNetwork.Pass.csproj b/DysonNetwork.Pass/DysonNetwork.Pass.csproj new file mode 100644 index 0000000..6869e9f --- /dev/null +++ b/DysonNetwork.Pass/DysonNetwork.Pass.csproj @@ -0,0 +1,42 @@ + + + + net9.0 + enable + enable + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/DysonNetwork.Pass/DysonNetwork.Pass.http b/DysonNetwork.Pass/DysonNetwork.Pass.http new file mode 100644 index 0000000..067647c --- /dev/null +++ b/DysonNetwork.Pass/DysonNetwork.Pass.http @@ -0,0 +1,6 @@ +@DysonNetwork.Pass_HostAddress = http://localhost:5216 + +GET {{DysonNetwork.Pass_HostAddress}}/weatherforecast/ +Accept: application/json + +### diff --git a/DysonNetwork.Pass/Handlers/ActionLogFlushHandler.cs b/DysonNetwork.Pass/Handlers/ActionLogFlushHandler.cs new file mode 100644 index 0000000..d5e9f49 --- /dev/null +++ b/DysonNetwork.Pass/Handlers/ActionLogFlushHandler.cs @@ -0,0 +1,25 @@ +using DysonNetwork.Pass.Account; +using DysonNetwork.Shared.Cache; +using EFCore.BulkExtensions; +using Quartz; + +namespace DysonNetwork.Pass.Handlers; + +public class ActionLogFlushHandler(IServiceProvider serviceProvider) : IFlushHandler +{ + public async Task FlushAsync(IReadOnlyList items) + { + using var scope = serviceProvider.CreateScope(); + var db = scope.ServiceProvider.GetRequiredService(); + + await db.BulkInsertAsync(items, config => config.ConflictOption = ConflictOption.Ignore); + } +} + +public class ActionLogFlushJob(FlushBufferService fbs, ActionLogFlushHandler hdl) : IJob +{ + public async Task Execute(IJobExecutionContext context) + { + await fbs.FlushAsync(hdl); + } +} \ No newline at end of file diff --git a/DysonNetwork.Pass/Handlers/LastActiveFlushHandler.cs b/DysonNetwork.Pass/Handlers/LastActiveFlushHandler.cs new file mode 100644 index 0000000..216a2c9 --- /dev/null +++ b/DysonNetwork.Pass/Handlers/LastActiveFlushHandler.cs @@ -0,0 +1,61 @@ +using DysonNetwork.Shared.Cache; +using Microsoft.EntityFrameworkCore; +using NodaTime; +using Quartz; + +namespace DysonNetwork.Pass.Handlers; + +public class LastActiveInfo +{ + public Auth.Session Session { get; set; } = null!; + public Account.Account Account { get; set; } = null!; + public Instant SeenAt { get; set; } +} + +public class LastActiveFlushHandler(IServiceProvider serviceProvider) : IFlushHandler +{ + public async Task FlushAsync(IReadOnlyList items) + { + using var scope = serviceProvider.CreateScope(); + var db = scope.ServiceProvider.GetRequiredService(); + + // Remove duplicates by grouping on (sessionId, accountId), taking the most recent SeenAt + var distinctItems = items + .GroupBy(x => (SessionId: x.Session.Id, AccountId: x.Account.Id)) + .Select(g => g.OrderByDescending(x => x.SeenAt).First()) + .ToList(); + + // Build dictionaries so we can match session/account IDs to their new "last seen" timestamps + var sessionIdMap = distinctItems + .GroupBy(x => x.Session.Id) + .ToDictionary(g => g.Key, g => g.Last().SeenAt); + + var accountIdMap = distinctItems + .GroupBy(x => x.Account.Id) + .ToDictionary(g => g.Key, g => g.Last().SeenAt); + + // Update sessions using native EF Core ExecuteUpdateAsync + foreach (var kvp in sessionIdMap) + { + await db.AuthSessions + .Where(s => s.Id == kvp.Key) + .ExecuteUpdateAsync(s => s.SetProperty(x => x.LastGrantedAt, kvp.Value)); + } + + // Update account profiles using native EF Core ExecuteUpdateAsync + foreach (var kvp in accountIdMap) + { + await db.AccountProfiles + .Where(a => a.AccountId == kvp.Key) + .ExecuteUpdateAsync(a => a.SetProperty(x => x.LastSeenAt, kvp.Value)); + } + } +} + +public class LastActiveFlushJob(FlushBufferService fbs, ActionLogFlushHandler hdl) : IJob +{ + public async Task Execute(IJobExecutionContext context) + { + await fbs.FlushAsync(hdl); + } +} \ No newline at end of file diff --git a/DysonNetwork.Pass/Permission/Permission.cs b/DysonNetwork.Pass/Permission/Permission.cs new file mode 100644 index 0000000..8a0eb4a --- /dev/null +++ b/DysonNetwork.Pass/Permission/Permission.cs @@ -0,0 +1,59 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Text.Json; +using System.Text.Json.Serialization; +using Microsoft.EntityFrameworkCore; +using NodaTime; + +namespace DysonNetwork.Pass.Permission; + +/// The permission node model provides the infrastructure of permission control in Dyson Network. +/// It based on the ABAC permission model. +/// +/// The value can be any type, boolean and number for most cases and stored in jsonb. +/// +/// The area represents the region this permission affects. For example, the pub:<publisherId> +/// indicates it's a permission node for the publishers managing. +/// +/// And the actor shows who owns the permission, in most cases, the user:<userId> +/// and when the permission node has a GroupId, the actor will be set to the group, but it won't work on checking +/// expect the member of that permission group inherent the permission from the group. +[Index(nameof(Key), nameof(Area), nameof(Actor))] +public class PermissionNode : ModelBase, IDisposable +{ + public Guid Id { get; set; } = Guid.NewGuid(); + [MaxLength(1024)] public string Actor { get; set; } = null!; + [MaxLength(1024)] public string Area { get; set; } = null!; + [MaxLength(1024)] public string Key { get; set; } = null!; + [Column(TypeName = "jsonb")] public JsonDocument Value { get; set; } = null!; + public Instant? ExpiredAt { get; set; } = null; + public Instant? AffectedAt { get; set; } = null; + + public Guid? GroupId { get; set; } = null; + [JsonIgnore] public PermissionGroup? Group { get; set; } = null; + + public void Dispose() + { + Value.Dispose(); + GC.SuppressFinalize(this); + } +} + +public class PermissionGroup : ModelBase +{ + public Guid Id { get; set; } = Guid.NewGuid(); + [MaxLength(1024)] public string Key { get; set; } = null!; + + public ICollection Nodes { get; set; } = new List(); + [JsonIgnore] public ICollection Members { get; set; } = new List(); +} + +public class PermissionGroupMember : ModelBase +{ + public Guid GroupId { get; set; } + public PermissionGroup Group { get; set; } = null!; + [MaxLength(1024)] public string Actor { get; set; } = null!; + + public Instant? ExpiredAt { get; set; } + public Instant? AffectedAt { get; set; } +} \ No newline at end of file diff --git a/DysonNetwork.Pass/Permission/PermissionMiddleware.cs b/DysonNetwork.Pass/Permission/PermissionMiddleware.cs new file mode 100644 index 0000000..e1011fc --- /dev/null +++ b/DysonNetwork.Pass/Permission/PermissionMiddleware.cs @@ -0,0 +1,51 @@ +namespace DysonNetwork.Pass.Permission; + +using System; + +[AttributeUsage(AttributeTargets.Method, Inherited = true)] +public class RequiredPermissionAttribute(string area, string key) : Attribute +{ + public string Area { get; set; } = area; + public string Key { get; } = key; +} + +public class PermissionMiddleware(RequestDelegate next) +{ + public async Task InvokeAsync(HttpContext httpContext, PermissionService pm) + { + var endpoint = httpContext.GetEndpoint(); + + var attr = endpoint?.Metadata + .OfType() + .FirstOrDefault(); + + if (attr != null) + { + if (httpContext.Items["CurrentUser"] is not Account.Account currentUser) + { + httpContext.Response.StatusCode = StatusCodes.Status403Forbidden; + await httpContext.Response.WriteAsync("Unauthorized"); + return; + } + + if (currentUser.IsSuperuser) + { + // Bypass the permission check for performance + await next(httpContext); + return; + } + + var actor = $"user:{currentUser.Id}"; + var permNode = await pm.GetPermissionAsync(actor, attr.Area, attr.Key); + + if (!permNode) + { + httpContext.Response.StatusCode = StatusCodes.Status403Forbidden; + await httpContext.Response.WriteAsync($"Permission {attr.Area}/{attr.Key} = {true} was required."); + return; + } + } + + await next(httpContext); + } +} \ No newline at end of file diff --git a/DysonNetwork.Pass/Permission/PermissionService.cs b/DysonNetwork.Pass/Permission/PermissionService.cs new file mode 100644 index 0000000..0ff2812 --- /dev/null +++ b/DysonNetwork.Pass/Permission/PermissionService.cs @@ -0,0 +1,198 @@ +using Microsoft.EntityFrameworkCore; +using NodaTime; +using System.Text.Json; +using DysonNetwork.Pass; +using DysonNetwork.Shared.Cache; + +namespace DysonNetwork.Pass.Permission; + +public class PermissionService( + AppDatabase db, + ICacheService cache +) +{ + private static readonly TimeSpan CacheExpiration = TimeSpan.FromMinutes(1); + + private const string PermCacheKeyPrefix = "perm:"; + private const string PermGroupCacheKeyPrefix = "perm-cg:"; + private const string PermissionGroupPrefix = "perm-g:"; + + private static string _GetPermissionCacheKey(string actor, string area, string key) => + PermCacheKeyPrefix + actor + ":" + area + ":" + key; + + private static string _GetGroupsCacheKey(string actor) => + PermGroupCacheKeyPrefix + actor; + + private static string _GetPermissionGroupKey(string actor) => + PermissionGroupPrefix + actor; + + public async Task HasPermissionAsync(string actor, string area, string key) + { + var value = await GetPermissionAsync(actor, area, key); + return value; + } + + public async Task GetPermissionAsync(string actor, string area, string key) + { + var cacheKey = _GetPermissionCacheKey(actor, area, key); + + var (hit, cachedValue) = await cache.GetAsyncWithStatus(cacheKey); + if (hit) + return cachedValue; + + var now = SystemClock.Instance.GetCurrentInstant(); + var groupsKey = _GetGroupsCacheKey(actor); + + var groupsId = await cache.GetAsync>(groupsKey); + if (groupsId == null) + { + groupsId = await db.PermissionGroupMembers + .Where(n => n.Actor == actor) + .Where(n => n.ExpiredAt == null || n.ExpiredAt > now) + .Where(n => n.AffectedAt == null || n.AffectedAt <= now) + .Select(e => e.GroupId) + .ToListAsync(); + + await cache.SetWithGroupsAsync(groupsKey, groupsId, + [_GetPermissionGroupKey(actor)], + CacheExpiration); + } + + var permission = await db.PermissionNodes + .Where(n => (n.GroupId == null && n.Actor == actor) || + (n.GroupId != null && groupsId.Contains(n.GroupId.Value))) + .Where(n => n.Key == key && n.Area == area) + .Where(n => n.ExpiredAt == null || n.ExpiredAt > now) + .Where(n => n.AffectedAt == null || n.AffectedAt <= now) + .FirstOrDefaultAsync(); + + var result = permission is not null ? _DeserializePermissionValue(permission.Value) : default; + + await cache.SetWithGroupsAsync(cacheKey, result, + [_GetPermissionGroupKey(actor)], + CacheExpiration); + + return result; + } + + public async Task AddPermissionNode( + string actor, + string area, + string key, + T value, + Instant? expiredAt = null, + Instant? affectedAt = null + ) + { + if (value is null) throw new ArgumentNullException(nameof(value)); + + var node = new PermissionNode + { + Actor = actor, + Key = key, + Area = area, + Value = _SerializePermissionValue(value), + ExpiredAt = expiredAt, + AffectedAt = affectedAt + }; + + db.PermissionNodes.Add(node); + await db.SaveChangesAsync(); + + // Invalidate related caches + await InvalidatePermissionCacheAsync(actor, area, key); + + return node; + } + + public async Task AddPermissionNodeToGroup( + PermissionGroup group, + string actor, + string area, + string key, + T value, + Instant? expiredAt = null, + Instant? affectedAt = null + ) + { + if (value is null) throw new ArgumentNullException(nameof(value)); + + var node = new PermissionNode + { + Actor = actor, + Key = key, + Area = area, + Value = _SerializePermissionValue(value), + ExpiredAt = expiredAt, + AffectedAt = affectedAt, + Group = group, + GroupId = group.Id + }; + + db.PermissionNodes.Add(node); + await db.SaveChangesAsync(); + + // Invalidate related caches + await InvalidatePermissionCacheAsync(actor, area, key); + await cache.RemoveAsync(_GetGroupsCacheKey(actor)); + await cache.RemoveGroupAsync(_GetPermissionGroupKey(actor)); + + return node; + } + + public async Task RemovePermissionNode(string actor, string area, string key) + { + var node = await db.PermissionNodes + .Where(n => n.Actor == actor && n.Area == area && n.Key == key) + .FirstOrDefaultAsync(); + if (node is not null) db.PermissionNodes.Remove(node); + await db.SaveChangesAsync(); + + // Invalidate cache + await InvalidatePermissionCacheAsync(actor, area, key); + } + + public async Task RemovePermissionNodeFromGroup(PermissionGroup group, string actor, string area, string key) + { + var node = await db.PermissionNodes + .Where(n => n.GroupId == group.Id) + .Where(n => n.Actor == actor && n.Area == area && n.Key == key) + .FirstOrDefaultAsync(); + if (node is null) return; + db.PermissionNodes.Remove(node); + await db.SaveChangesAsync(); + + // Invalidate caches + await InvalidatePermissionCacheAsync(actor, area, key); + await cache.RemoveAsync(_GetGroupsCacheKey(actor)); + await cache.RemoveGroupAsync(_GetPermissionGroupKey(actor)); + } + + private async Task InvalidatePermissionCacheAsync(string actor, string area, string key) + { + var cacheKey = _GetPermissionCacheKey(actor, area, key); + await cache.RemoveAsync(cacheKey); + } + + private static T? _DeserializePermissionValue(JsonDocument json) + { + return JsonSerializer.Deserialize(json.RootElement.GetRawText()); + } + + private static JsonDocument _SerializePermissionValue(T obj) + { + var str = JsonSerializer.Serialize(obj); + return JsonDocument.Parse(str); + } + + public static PermissionNode NewPermissionNode(string actor, string area, string key, T value) + { + return new PermissionNode + { + Actor = actor, + Area = area, + Key = key, + Value = _SerializePermissionValue(value), + }; + } +} \ No newline at end of file diff --git a/DysonNetwork.Pass/Program.cs b/DysonNetwork.Pass/Program.cs new file mode 100644 index 0000000..666a9c5 --- /dev/null +++ b/DysonNetwork.Pass/Program.cs @@ -0,0 +1,23 @@ +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. + +builder.Services.AddControllers(); +// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi +builder.Services.AddOpenApi(); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.MapOpenApi(); +} + +app.UseHttpsRedirection(); + +app.UseAuthorization(); + +app.MapControllers(); + +app.Run(); diff --git a/DysonNetwork.Pass/Properties/launchSettings.json b/DysonNetwork.Pass/Properties/launchSettings.json new file mode 100644 index 0000000..23c7313 --- /dev/null +++ b/DysonNetwork.Pass/Properties/launchSettings.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "http://localhost:5216", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "https://localhost:7058;http://localhost:5216", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/DysonNetwork.Pass/WeatherForecast.cs b/DysonNetwork.Pass/WeatherForecast.cs new file mode 100644 index 0000000..74ce1c5 --- /dev/null +++ b/DysonNetwork.Pass/WeatherForecast.cs @@ -0,0 +1,12 @@ +namespace DysonNetwork.Pass; + +public class WeatherForecast +{ + public DateOnly Date { get; set; } + + public int TemperatureC { get; set; } + + public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); + + public string? Summary { get; set; } +} diff --git a/DysonNetwork.Pass/appsettings.Development.json b/DysonNetwork.Pass/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/DysonNetwork.Pass/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/DysonNetwork.Pass/appsettings.json b/DysonNetwork.Pass/appsettings.json new file mode 100644 index 0000000..10f68b8 --- /dev/null +++ b/DysonNetwork.Pass/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/DysonNetwork.Shared/Cache/CacheService.cs b/DysonNetwork.Shared/Cache/CacheService.cs new file mode 100644 index 0000000..2157bb7 --- /dev/null +++ b/DysonNetwork.Shared/Cache/CacheService.cs @@ -0,0 +1,396 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; +using NodaTime; +using NodaTime.Serialization.JsonNet; +using StackExchange.Redis; + +namespace DysonNetwork.Shared.Cache; + +/// +/// Represents a distributed lock that can be used to synchronize access across multiple processes +/// +public interface IDistributedLock : IAsyncDisposable +{ + /// + /// The resource identifier this lock is protecting + /// + string Resource { get; } + + /// + /// Unique identifier for this lock instance + /// + string LockId { get; } + + /// + /// Extends the lock's expiration time + /// + Task ExtendAsync(TimeSpan timeSpan); + + /// + /// Releases the lock immediately + /// + Task ReleaseAsync(); +} + +public interface ICacheService +{ + /// + /// Sets a value in the cache with an optional expiration time + /// + Task SetAsync(string key, T value, TimeSpan? expiry = null); + + /// + /// Gets a value from the cache + /// + Task GetAsync(string key); + + /// + /// Get a value from the cache with the found status + /// + Task<(bool found, T? value)> GetAsyncWithStatus(string key); + + /// + /// Removes a specific key from the cache + /// + Task RemoveAsync(string key); + + /// + /// Adds a key to a group for group-based operations + /// + Task AddToGroupAsync(string key, string group); + + /// + /// Removes all keys associated with a specific group + /// + Task RemoveGroupAsync(string group); + + /// + /// Gets all keys belonging to a specific group + /// + Task> GetGroupKeysAsync(string group); + + /// + /// Helper method to set a value in cache and associate it with multiple groups in one operation + /// + /// The type of value being cached + /// Cache key + /// The value to cache + /// Optional collection of group names to associate the key with + /// Optional expiration time for the cached item + /// True if the set operation was successful + Task SetWithGroupsAsync(string key, T value, IEnumerable? groups = null, TimeSpan? expiry = null); + + /// + /// Acquires a distributed lock on the specified resource + /// + /// The resource identifier to lock + /// How long the lock should be held before automatically expiring + /// How long to wait for the lock before giving up + /// How often to retry acquiring the lock during the wait time + /// A distributed lock instance if acquired, null otherwise + Task AcquireLockAsync(string resource, TimeSpan expiry, TimeSpan? waitTime = null, + TimeSpan? retryInterval = null); + + /// + /// Executes an action with a distributed lock, ensuring the lock is properly released afterwards + /// + /// The resource identifier to lock + /// The action to execute while holding the lock + /// How long the lock should be held before automatically expiring + /// How long to wait for the lock before giving up + /// How often to retry acquiring the lock during the wait time + /// True if the lock was acquired and the action was executed, false otherwise + Task ExecuteWithLockAsync(string resource, Func action, TimeSpan expiry, TimeSpan? waitTime = null, + TimeSpan? retryInterval = null); + + /// + /// Executes a function with a distributed lock, ensuring the lock is properly released afterwards + /// + /// The return type of the function + /// The resource identifier to lock + /// The function to execute while holding the lock + /// How long the lock should be held before automatically expiring + /// How long to wait for the lock before giving up + /// How often to retry acquiring the lock during the wait time + /// The result of the function if the lock was acquired, default(T) otherwise + Task<(bool Acquired, T? Result)> ExecuteWithLockAsync(string resource, Func> func, TimeSpan expiry, + TimeSpan? waitTime = null, TimeSpan? retryInterval = null); +} + +public class RedisDistributedLock : IDistributedLock +{ + private readonly IDatabase _database; + private bool _disposed; + + public string Resource { get; } + public string LockId { get; } + + internal RedisDistributedLock(IDatabase database, string resource, string lockId) + { + _database = database; + Resource = resource; + LockId = lockId; + } + + public async Task ExtendAsync(TimeSpan timeSpan) + { + if (_disposed) + throw new ObjectDisposedException(nameof(RedisDistributedLock)); + + var script = @" + if redis.call('get', KEYS[1]) == ARGV[1] then + return redis.call('pexpire', KEYS[1], ARGV[2]) + else + return 0 + end + "; + + var result = await _database.ScriptEvaluateAsync( + script, + [$"{CacheServiceRedis.LockKeyPrefix}{Resource}"], + [LockId, (long)timeSpan.TotalMilliseconds] + ); + + return (long)result! == 1; + } + + public async Task ReleaseAsync() + { + if (_disposed) + return; + + var script = @" + if redis.call('get', KEYS[1]) == ARGV[1] then + return redis.call('del', KEYS[1]) + else + return 0 + end + "; + + await _database.ScriptEvaluateAsync( + script, + [$"{CacheServiceRedis.LockKeyPrefix}{Resource}"], + [LockId] + ); + + _disposed = true; + } + + public async ValueTask DisposeAsync() + { + await ReleaseAsync(); + GC.SuppressFinalize(this); + } +} + +public class CacheServiceRedis : ICacheService +{ + private readonly IDatabase _database; + private readonly JsonSerializerSettings _serializerSettings; + + // Global prefix for all cache keys + public const string GlobalKeyPrefix = "dyson:"; + + // Using prefixes for different types of keys + public const string GroupKeyPrefix = GlobalKeyPrefix + "cg:"; + public const string LockKeyPrefix = GlobalKeyPrefix + "lock:"; + + public CacheServiceRedis(IConnectionMultiplexer redis) + { + var rds = redis ?? throw new ArgumentNullException(nameof(redis)); + _database = rds.GetDatabase(); + + // Configure Newtonsoft.Json with proper NodaTime serialization + _serializerSettings = new JsonSerializerSettings + { + ContractResolver = new CamelCasePropertyNamesContractResolver(), + PreserveReferencesHandling = PreserveReferencesHandling.Objects, + NullValueHandling = NullValueHandling.Include, + DateParseHandling = DateParseHandling.None + }; + + // Configure NodaTime serializers + _serializerSettings.ConfigureForNodaTime(DateTimeZoneProviders.Tzdb); + } + + public async Task SetAsync(string key, T value, TimeSpan? expiry = null) + { + key = $"{GlobalKeyPrefix}{key}"; + if (string.IsNullOrEmpty(key)) + throw new ArgumentException("Key cannot be null or empty", nameof(key)); + + var serializedValue = JsonConvert.SerializeObject(value, _serializerSettings); + return await _database.StringSetAsync(key, serializedValue, expiry); + } + + public async Task GetAsync(string key) + { + key = $"{GlobalKeyPrefix}{key}"; + if (string.IsNullOrEmpty(key)) + throw new ArgumentException("Key cannot be null or empty", nameof(key)); + + var value = await _database.StringGetAsync(key); + + if (value.IsNullOrEmpty) + return default; + + // For NodaTime serialization, use the configured serializer settings + return JsonConvert.DeserializeObject(value!, _serializerSettings); + } + + public async Task<(bool found, T? value)> GetAsyncWithStatus(string key) + { + key = $"{GlobalKeyPrefix}{key}"; + if (string.IsNullOrEmpty(key)) + throw new ArgumentException("Key cannot be null or empty", nameof(key)); + + var value = await _database.StringGetAsync(key); + + if (value.IsNullOrEmpty) + return (false, default); + + // For NodaTime serialization, use the configured serializer settings + return (true, JsonConvert.DeserializeObject(value!, _serializerSettings)); + } + + public async Task RemoveAsync(string key) + { + key = $"{GlobalKeyPrefix}{key}"; + if (string.IsNullOrEmpty(key)) + throw new ArgumentException("Key cannot be null or empty", nameof(key)); + + // Before removing the key, find all groups it belongs to and remove it from them + var script = @" + local groups = redis.call('KEYS', ARGV[1]) + for _, group in ipairs(groups) do + redis.call('SREM', group, ARGV[2]) + end + return redis.call('DEL', ARGV[2]) + "; + + var result = await _database.ScriptEvaluateAsync( + script, + values: [$"{GroupKeyPrefix}*", key] + ); + + return (long)result! > 0; + } + + public async Task AddToGroupAsync(string key, string group) + { + if (string.IsNullOrEmpty(key)) + throw new ArgumentException(@"Key cannot be null or empty.", nameof(key)); + + if (string.IsNullOrEmpty(group)) + throw new ArgumentException(@"Group cannot be null or empty.", nameof(group)); + + var groupKey = $"{GroupKeyPrefix}{group}"; + key = $"{GlobalKeyPrefix}{key}"; + await _database.SetAddAsync(groupKey, key); + } + + public async Task RemoveGroupAsync(string group) + { + if (string.IsNullOrEmpty(group)) + throw new ArgumentException(@"Group cannot be null or empty.", nameof(group)); + + var groupKey = $"{GroupKeyPrefix}{group}"; + + // Get all keys in the group + var keys = await _database.SetMembersAsync(groupKey); + + if (keys.Length > 0) + { + // Delete all the keys + var keysTasks = keys.Select(key => _database.KeyDeleteAsync(key.ToString())); + await Task.WhenAll(keysTasks); + } + + // Delete the group itself + await _database.KeyDeleteAsync(groupKey); + } + + public async Task> GetGroupKeysAsync(string group) + { + if (string.IsNullOrEmpty(group)) + throw new ArgumentException(@"Group cannot be null or empty.", nameof(group)); + + var groupKey = $"{GroupKeyPrefix}{group}"; + var members = await _database.SetMembersAsync(groupKey); + + return members.Select(m => m.ToString()); + } + + public async Task SetWithGroupsAsync(string key, T value, IEnumerable? groups = null, + TimeSpan? expiry = null) + { + // First, set the value in the cache + var setResult = await SetAsync(key, value, expiry); + + // If successful and there are groups to associate, add the key to each group + if (!setResult || groups == null) return setResult; + var groupsArray = groups.Where(g => !string.IsNullOrEmpty(g)).ToArray(); + if (groupsArray.Length <= 0) return setResult; + var tasks = groupsArray.Select(group => AddToGroupAsync(key, group)); + await Task.WhenAll(tasks); + + return setResult; + } + + public async Task AcquireLockAsync(string resource, TimeSpan expiry, TimeSpan? waitTime = null, + TimeSpan? retryInterval = null) + { + if (string.IsNullOrEmpty(resource)) + throw new ArgumentException("Resource cannot be null or empty", nameof(resource)); + + var lockKey = $"{LockKeyPrefix}{resource}"; + var lockId = Guid.NewGuid().ToString("N"); + var waitTimeSpan = waitTime ?? TimeSpan.Zero; + var retryIntervalSpan = retryInterval ?? TimeSpan.FromMilliseconds(100); + + var startTime = DateTime.UtcNow; + var acquired = false; + + // Try to acquire the lock, retry until waitTime is exceeded + while (!acquired && (DateTime.UtcNow - startTime) < waitTimeSpan) + { + acquired = await _database.StringSetAsync(lockKey, lockId, expiry, When.NotExists); + + if (!acquired) + { + await Task.Delay(retryIntervalSpan); + } + } + + if (!acquired) + { + return null; // Could not acquire the lock within the wait time + } + + return new RedisDistributedLock(_database, resource, lockId); + } + + public async Task ExecuteWithLockAsync(string resource, Func action, TimeSpan expiry, + TimeSpan? waitTime = null, TimeSpan? retryInterval = null) + { + await using var lockObj = await AcquireLockAsync(resource, expiry, waitTime, retryInterval); + + if (lockObj == null) + return false; // Could not acquire the lock + + await action(); + return true; + } + + public async Task<(bool Acquired, T? Result)> ExecuteWithLockAsync(string resource, Func> func, + TimeSpan expiry, TimeSpan? waitTime = null, TimeSpan? retryInterval = null) + { + await using var lockObj = await AcquireLockAsync(resource, expiry, waitTime, retryInterval); + + if (lockObj == null) + return (false, default); // Could not acquire the lock + + var result = await func(); + return (true, result); + } +} \ No newline at end of file diff --git a/DysonNetwork.Shared/Cache/FlushBufferService.cs b/DysonNetwork.Shared/Cache/FlushBufferService.cs new file mode 100644 index 0000000..fc7810d --- /dev/null +++ b/DysonNetwork.Shared/Cache/FlushBufferService.cs @@ -0,0 +1,66 @@ +using System.Collections.Concurrent; + +namespace DysonNetwork.Shared.Cache; + +public interface IFlushHandler +{ + Task FlushAsync(IReadOnlyList items); +} + +public class FlushBufferService +{ + private readonly Dictionary _buffers = new(); + private readonly Lock _lockObject = new(); + + private ConcurrentQueue _GetOrCreateBuffer() + { + var type = typeof(T); + lock (_lockObject) + { + if (!_buffers.TryGetValue(type, out var buffer)) + { + buffer = new ConcurrentQueue(); + _buffers[type] = buffer; + } + return (ConcurrentQueue)buffer; + } + } + + public void Enqueue(T item) + { + var buffer = _GetOrCreateBuffer(); + buffer.Enqueue(item); + } + + public async Task FlushAsync(IFlushHandler handler) + { + var buffer = _GetOrCreateBuffer(); + var workingQueue = new List(); + + while (buffer.TryDequeue(out var item)) + { + workingQueue.Add(item); + } + + if (workingQueue.Count == 0) + return; + + try + { + await handler.FlushAsync(workingQueue); + } + catch (Exception) + { + // If flush fails, re-queue the items + foreach (var item in workingQueue) + buffer.Enqueue(item); + throw; + } + } + + public int GetPendingCount() + { + var buffer = _GetOrCreateBuffer(); + return buffer.Count; + } +} \ No newline at end of file diff --git a/DysonNetwork.Shared/DysonNetwork.Shared.csproj b/DysonNetwork.Shared/DysonNetwork.Shared.csproj new file mode 100644 index 0000000..6092e6f --- /dev/null +++ b/DysonNetwork.Shared/DysonNetwork.Shared.csproj @@ -0,0 +1,18 @@ + + + + net9.0 + enable + enable + + + + + + + + + + + + diff --git a/DysonNetwork.Shared/Geo/GeoIpService.cs b/DysonNetwork.Shared/Geo/GeoIpService.cs new file mode 100644 index 0000000..9507bf2 --- /dev/null +++ b/DysonNetwork.Shared/Geo/GeoIpService.cs @@ -0,0 +1,56 @@ +using MaxMind.GeoIP2; +using Microsoft.Extensions.Options; +using NetTopologySuite.Geometries; +using Point = NetTopologySuite.Geometries.Point; + +namespace DysonNetwork.Shared.Geo; + +public class GeoIpOptions +{ + public string DatabasePath { get; set; } = null!; +} + +public class GeoIpService(IOptions options) +{ + private readonly string _databasePath = options.Value.DatabasePath; + private readonly GeometryFactory _geometryFactory = new(new PrecisionModel(), 4326); // 4326 is the SRID for WGS84 + + public Point? GetPointFromIp(string? ipAddress) + { + if (string.IsNullOrEmpty(ipAddress)) + return null; + + try + { + using var reader = new DatabaseReader(_databasePath); + var city = reader.City(ipAddress); + + if (city?.Location == null || !city.Location.HasCoordinates) + return null; + + return _geometryFactory.CreatePoint(new Coordinate( + city.Location.Longitude ?? 0, + city.Location.Latitude ?? 0)); + } + catch (Exception) + { + return null; + } + } + + public MaxMind.GeoIP2.Responses.CityResponse? GetFromIp(string? ipAddress) + { + if (string.IsNullOrEmpty(ipAddress)) + return null; + + try + { + using var reader = new DatabaseReader(_databasePath); + return reader.City(ipAddress); + } + catch (Exception) + { + return null; + } + } +} \ No newline at end of file diff --git a/DysonNetwork.Sphere/DysonNetwork.Sphere.csproj b/DysonNetwork.Sphere/DysonNetwork.Sphere.csproj index d1c90df..f7c5de9 100644 --- a/DysonNetwork.Sphere/DysonNetwork.Sphere.csproj +++ b/DysonNetwork.Sphere/DysonNetwork.Sphere.csproj @@ -34,7 +34,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -69,7 +69,7 @@ - + diff --git a/DysonNetwork.Sphere/Storage/FileController.cs b/DysonNetwork.Sphere/Storage/FileController.cs index 32f14c6..d13f531 100644 --- a/DysonNetwork.Sphere/Storage/FileController.cs +++ b/DysonNetwork.Sphere/Storage/FileController.cs @@ -26,11 +26,11 @@ public class FileController( { // Support the file extension for client side data recognize string? fileExtension = null; - if (id.Contains(".")) + if (id.Contains('.')) { - var splitedId = id.Split('.'); - id = splitedId.First(); - fileExtension = splitedId.Last(); + var splitId = id.Split('.'); + id = splitId.First(); + fileExtension = splitId.Last(); } var file = await fs.GetFileAsync(id); diff --git a/DysonNetwork.sln b/DysonNetwork.sln index ea7c8d0..7ee589d 100644 --- a/DysonNetwork.sln +++ b/DysonNetwork.sln @@ -7,6 +7,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution compose.yaml = compose.yaml EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DysonNetwork.Pass", "DysonNetwork.Pass\DysonNetwork.Pass.csproj", "{A8F37E9E-52A4-4159-8227-F2F65CBA0606}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DysonNetwork.Shared", "DysonNetwork.Shared\DysonNetwork.Shared.csproj", "{DB46D1A6-79B4-43FC-A9A9-115CDA26947A}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -17,5 +21,13 @@ Global {CFF62EFA-F4C2-4FC7-8D97-25570B4DB452}.Debug|Any CPU.Build.0 = Debug|Any CPU {CFF62EFA-F4C2-4FC7-8D97-25570B4DB452}.Release|Any CPU.ActiveCfg = Release|Any CPU {CFF62EFA-F4C2-4FC7-8D97-25570B4DB452}.Release|Any CPU.Build.0 = Release|Any CPU + {A8F37E9E-52A4-4159-8227-F2F65CBA0606}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A8F37E9E-52A4-4159-8227-F2F65CBA0606}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A8F37E9E-52A4-4159-8227-F2F65CBA0606}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A8F37E9E-52A4-4159-8227-F2F65CBA0606}.Release|Any CPU.Build.0 = Release|Any CPU + {DB46D1A6-79B4-43FC-A9A9-115CDA26947A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DB46D1A6-79B4-43FC-A9A9-115CDA26947A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DB46D1A6-79B4-43FC-A9A9-115CDA26947A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DB46D1A6-79B4-43FC-A9A9-115CDA26947A}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/DysonNetwork.sln.DotSettings.user b/DysonNetwork.sln.DotSettings.user index 897f1de..069ceae 100644 --- a/DysonNetwork.sln.DotSettings.user +++ b/DysonNetwork.sln.DotSettings.user @@ -146,4 +146,4 @@ True False - True \ No newline at end of file + False \ No newline at end of file From ba49d1c7a70c04a20b4a335b1caefe691d35f63d Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Sat, 12 Jul 2025 11:40:18 +0800 Subject: [PATCH 02/42] :recycle: Basically completed the separate of account service --- DysonNetwork.Pass/Account/AbuseReport.cs | 1 + DysonNetwork.Pass/Account/Account.cs | 7 +- .../Account/AccountController.cs | 4 +- .../Account/AccountCurrentController.cs | 81 +--- .../Account/AccountEventService.cs | 32 +- DysonNetwork.Pass/Account/AccountService.cs | 10 +- DysonNetwork.Pass/Account/ActionLog.cs | 1 + DysonNetwork.Pass/Account/ActionLogService.cs | 8 +- DysonNetwork.Pass/Account/Badge.cs | 3 +- DysonNetwork.Pass/Account/Event.cs | 1 + DysonNetwork.Pass/Account/MagicSpell.cs | 1 + .../Account/MagicSpellService.cs | 119 +++-- DysonNetwork.Pass/Account/Notification.cs | 1 + .../Account/NotificationController.cs | 5 +- .../Account/NotificationService.cs | 22 +- DysonNetwork.Pass/Account/Relationship.cs | 1 + DysonNetwork.Pass/AppDatabase.cs | 27 +- DysonNetwork.Pass/Auth/Auth.cs | 2 +- DysonNetwork.Pass/Auth/AuthController.cs | 10 +- DysonNetwork.Pass/Auth/AuthService.cs | 10 +- DysonNetwork.Pass/Auth/CompactTokenService.cs | 2 +- .../Controllers/OidcProviderController.cs | 2 +- .../Services/OidcProviderService.cs | 12 +- .../Auth/OpenId/OidcController.cs | 2 +- DysonNetwork.Pass/Auth/OpenId/OidcService.cs | 4 +- DysonNetwork.Pass/Auth/Session.cs | 9 +- DysonNetwork.Pass/DysonNetwork.Pass.csproj | 69 +++ DysonNetwork.Pass/Email/EmailModels.cs | 31 ++ DysonNetwork.Pass/Email/EmailService.cs | 106 +++++ DysonNetwork.Pass/Email/RazorViewRenderer.cs | 45 ++ .../Handlers/LastActiveFlushHandler.cs | 2 +- .../Localization/AccountEventResource.cs | 6 + .../Localization/EmailResource.cs | 5 + .../Localization/NotificationResource.cs | 6 + .../Localization/SharedResource.cs | 6 + .../Pages/Emails/AccountDeletionEmail.razor | 42 ++ .../Emails/ContactVerificationEmail.razor | 43 ++ .../Pages/Emails/EmailLayout.razor | 337 +++++++++++++ .../Pages/Emails/LandingEmail.razor | 43 ++ .../Pages/Emails/PasswordResetEmail.razor | 44 ++ .../Pages/Emails/VerificationEmail.razor | 27 ++ DysonNetwork.Pass/Permission/Permission.cs | 1 + .../AccountEventResource.Designer.cs | 222 +++++++++ .../Localization/AccountEventResource.resx | 113 +++++ .../AccountEventResource.zh-hans.resx | 98 ++++ .../Localization/EmailResource.Designer.cs | 90 ++++ .../Resources/Localization/EmailResource.resx | 126 +++++ .../Localization/EmailResource.zh-hans.resx | 119 +++++ .../NotificationResource.Designer.cs | 162 +++++++ .../Localization/NotificationResource.resx | 83 ++++ .../NotificationResource.zh-hans.resx | 75 +++ .../Localization/SharedResource.Designer.cs | 48 ++ .../Localization/SharedResource.resx | 21 + .../Localization/SharedResource.zh-hans.resx | 14 + DysonNetwork.Pass/Wallet/OrderController.cs | 57 +++ DysonNetwork.Pass/Wallet/Payment.cs | 61 +++ .../PaymentHandlers/AfdianPaymentHandler.cs | 446 ++++++++++++++++++ .../PaymentHandlers/ISubscriptionOrder.cs | 18 + DysonNetwork.Pass/Wallet/PaymentService.cs | 297 ++++++++++++ DysonNetwork.Pass/Wallet/Subscription.cs | 233 +++++++++ .../Wallet/SubscriptionController.cs | 204 ++++++++ .../Wallet/SubscriptionRenewalJob.cs | 137 ++++++ .../Wallet/SubscriptionService.cs | 394 ++++++++++++++++ DysonNetwork.Pass/Wallet/Wallet.cs | 25 + DysonNetwork.Pass/Wallet/WalletController.cs | 101 ++++ DysonNetwork.Pass/Wallet/WalletService.cs | 49 ++ .../Data/CloudFileReferenceObject.cs | 17 + DysonNetwork.Shared/Data/ICloudFile.cs | 55 +++ DysonNetwork.Shared/Data/ModelBase.cs | 15 + 69 files changed, 4245 insertions(+), 225 deletions(-) create mode 100644 DysonNetwork.Pass/Email/EmailModels.cs create mode 100644 DysonNetwork.Pass/Email/EmailService.cs create mode 100644 DysonNetwork.Pass/Email/RazorViewRenderer.cs create mode 100644 DysonNetwork.Pass/Localization/AccountEventResource.cs create mode 100644 DysonNetwork.Pass/Localization/EmailResource.cs create mode 100644 DysonNetwork.Pass/Localization/NotificationResource.cs create mode 100644 DysonNetwork.Pass/Localization/SharedResource.cs create mode 100644 DysonNetwork.Pass/Pages/Emails/AccountDeletionEmail.razor create mode 100644 DysonNetwork.Pass/Pages/Emails/ContactVerificationEmail.razor create mode 100644 DysonNetwork.Pass/Pages/Emails/EmailLayout.razor create mode 100644 DysonNetwork.Pass/Pages/Emails/LandingEmail.razor create mode 100644 DysonNetwork.Pass/Pages/Emails/PasswordResetEmail.razor create mode 100644 DysonNetwork.Pass/Pages/Emails/VerificationEmail.razor create mode 100644 DysonNetwork.Pass/Resources/Localization/AccountEventResource.Designer.cs create mode 100644 DysonNetwork.Pass/Resources/Localization/AccountEventResource.resx create mode 100644 DysonNetwork.Pass/Resources/Localization/AccountEventResource.zh-hans.resx create mode 100644 DysonNetwork.Pass/Resources/Localization/EmailResource.Designer.cs create mode 100644 DysonNetwork.Pass/Resources/Localization/EmailResource.resx create mode 100644 DysonNetwork.Pass/Resources/Localization/EmailResource.zh-hans.resx create mode 100644 DysonNetwork.Pass/Resources/Localization/NotificationResource.Designer.cs create mode 100644 DysonNetwork.Pass/Resources/Localization/NotificationResource.resx create mode 100644 DysonNetwork.Pass/Resources/Localization/NotificationResource.zh-hans.resx create mode 100644 DysonNetwork.Pass/Resources/Localization/SharedResource.Designer.cs create mode 100644 DysonNetwork.Pass/Resources/Localization/SharedResource.resx create mode 100644 DysonNetwork.Pass/Resources/Localization/SharedResource.zh-hans.resx create mode 100644 DysonNetwork.Pass/Wallet/OrderController.cs create mode 100644 DysonNetwork.Pass/Wallet/Payment.cs create mode 100644 DysonNetwork.Pass/Wallet/PaymentHandlers/AfdianPaymentHandler.cs create mode 100644 DysonNetwork.Pass/Wallet/PaymentHandlers/ISubscriptionOrder.cs create mode 100644 DysonNetwork.Pass/Wallet/PaymentService.cs create mode 100644 DysonNetwork.Pass/Wallet/Subscription.cs create mode 100644 DysonNetwork.Pass/Wallet/SubscriptionController.cs create mode 100644 DysonNetwork.Pass/Wallet/SubscriptionRenewalJob.cs create mode 100644 DysonNetwork.Pass/Wallet/SubscriptionService.cs create mode 100644 DysonNetwork.Pass/Wallet/Wallet.cs create mode 100644 DysonNetwork.Pass/Wallet/WalletController.cs create mode 100644 DysonNetwork.Pass/Wallet/WalletService.cs create mode 100644 DysonNetwork.Shared/Data/CloudFileReferenceObject.cs create mode 100644 DysonNetwork.Shared/Data/ICloudFile.cs create mode 100644 DysonNetwork.Shared/Data/ModelBase.cs diff --git a/DysonNetwork.Pass/Account/AbuseReport.cs b/DysonNetwork.Pass/Account/AbuseReport.cs index ebb7a26..bc14691 100644 --- a/DysonNetwork.Pass/Account/AbuseReport.cs +++ b/DysonNetwork.Pass/Account/AbuseReport.cs @@ -1,4 +1,5 @@ using System.ComponentModel.DataAnnotations; +using DysonNetwork.Shared.Data; using NodaTime; namespace DysonNetwork.Pass.Account; diff --git a/DysonNetwork.Pass/Account/Account.cs b/DysonNetwork.Pass/Account/Account.cs index 6674ae2..027e127 100644 --- a/DysonNetwork.Pass/Account/Account.cs +++ b/DysonNetwork.Pass/Account/Account.cs @@ -1,6 +1,7 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using System.Text.Json.Serialization; +using DysonNetwork.Shared.Data; using Microsoft.EntityFrameworkCore; using NodaTime; using OtpNet; @@ -19,12 +20,12 @@ public class Account : ModelBase public Profile Profile { get; set; } = null!; public ICollection Contacts { get; set; } = new List(); - public ICollection Badges { get; set; } = new List(); + public ICollection Badges { get; set; } = new List(); [JsonIgnore] public ICollection AuthFactors { get; set; } = new List(); [JsonIgnore] public ICollection Connections { get; set; } = new List(); - [JsonIgnore] public ICollection Sessions { get; set; } = new List(); - [JsonIgnore] public ICollection Challenges { get; set; } = new List(); + [JsonIgnore] public ICollection Sessions { get; set; } = new List(); + [JsonIgnore] public ICollection Challenges { get; set; } = new List(); [JsonIgnore] public ICollection OutgoingRelationships { get; set; } = new List(); [JsonIgnore] public ICollection IncomingRelationships { get; set; } = new List(); diff --git a/DysonNetwork.Pass/Account/AccountController.cs b/DysonNetwork.Pass/Account/AccountController.cs index b6eabf3..98a2564 100644 --- a/DysonNetwork.Pass/Account/AccountController.cs +++ b/DysonNetwork.Pass/Account/AccountController.cs @@ -34,9 +34,9 @@ public class AccountController( } [HttpGet("{name}/badges")] - [ProducesResponseType>(StatusCodes.Status200OK)] + [ProducesResponseType>(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task>> GetBadgesByName(string name) + public async Task>> GetBadgesByName(string name) { var account = await db.Accounts .Include(e => e.Badges) diff --git a/DysonNetwork.Pass/Account/AccountCurrentController.cs b/DysonNetwork.Pass/Account/AccountCurrentController.cs index 59642d2..037935d 100644 --- a/DysonNetwork.Pass/Account/AccountCurrentController.cs +++ b/DysonNetwork.Pass/Account/AccountCurrentController.cs @@ -1,12 +1,10 @@ using System.ComponentModel.DataAnnotations; using DysonNetwork.Pass.Auth; -using DysonNetwork.Pass; -using DysonNetwork.Pass.Storage; +using DysonNetwork.Pass.Permission; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using NodaTime; -using Org.BouncyCastle.Utilities; namespace DysonNetwork.Pass.Account; @@ -16,7 +14,6 @@ namespace DysonNetwork.Pass.Account; public class AccountCurrentController( AppDatabase db, AccountService accounts, - FileReferenceService fileRefService, AccountEventService events, AuthService auth ) : ControllerBase @@ -97,58 +94,12 @@ public class AccountCurrentController( if (request.PictureId is not null) { - var picture = await db.Files.Where(f => f.Id == request.PictureId).FirstOrDefaultAsync(); - if (picture is null) return BadRequest("Invalid picture id, unable to find the file on cloud."); - - var profileResourceId = $"profile:{profile.Id}"; - - // Remove old references for the profile picture - if (profile.Picture is not null) - { - var oldPictureRefs = - await fileRefService.GetResourceReferencesAsync(profileResourceId, "profile.picture"); - foreach (var oldRef in oldPictureRefs) - { - await fileRefService.DeleteReferenceAsync(oldRef.Id); - } - } - - profile.Picture = picture.ToReferenceObject(); - - // Create new reference - await fileRefService.CreateReferenceAsync( - picture.Id, - "profile.picture", - profileResourceId - ); + // TODO: Create reference, set profile picture } if (request.BackgroundId is not null) { - var background = await db.Files.Where(f => f.Id == request.BackgroundId).FirstOrDefaultAsync(); - if (background is null) return BadRequest("Invalid background id, unable to find the file on cloud."); - - var profileResourceId = $"profile:{profile.Id}"; - - // Remove old references for the profile background - if (profile.Background is not null) - { - var oldBackgroundRefs = - await fileRefService.GetResourceReferencesAsync(profileResourceId, "profile.background"); - foreach (var oldRef in oldBackgroundRefs) - { - await fileRefService.DeleteReferenceAsync(oldRef.Id); - } - } - - profile.Background = background.ToReferenceObject(); - - // Create new reference - await fileRefService.CreateReferenceAsync( - background.Id, - "profile.background", - profileResourceId - ); + // TODO: Create reference, set profile background } db.Update(profile); @@ -438,7 +389,7 @@ public class AccountCurrentController( public string UserAgent { get; set; } = null!; public string DeviceId { get; set; } = null!; public ChallengePlatform Platform { get; set; } - public List Sessions { get; set; } = []; + public List Sessions { get; set; } = []; } [HttpGet("devices")] @@ -446,7 +397,7 @@ public class AccountCurrentController( public async Task>> GetDevices() { if (HttpContext.Items["CurrentUser"] is not Account currentUser || - HttpContext.Items["CurrentSession"] is not Session currentSession) return Unauthorized(); + HttpContext.Items["CurrentSession"] is not AuthSession currentSession) return Unauthorized(); Response.Headers.Append("X-Auth-Session", currentSession.Id.ToString()); @@ -475,13 +426,13 @@ public class AccountCurrentController( [HttpGet("sessions")] [Authorize] - public async Task>> GetSessions( + public async Task>> GetSessions( [FromQuery] int take = 20, [FromQuery] int offset = 0 ) { if (HttpContext.Items["CurrentUser"] is not Account currentUser || - HttpContext.Items["CurrentSession"] is not Session currentSession) return Unauthorized(); + HttpContext.Items["CurrentSession"] is not AuthSession currentSession) return Unauthorized(); var query = db.AuthSessions .Include(session => session.Account) @@ -503,7 +454,7 @@ public class AccountCurrentController( [HttpDelete("sessions/{id:guid}")] [Authorize] - public async Task> DeleteSession(Guid id) + public async Task> DeleteSession(Guid id) { if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); @@ -520,10 +471,10 @@ public class AccountCurrentController( [HttpDelete("sessions/current")] [Authorize] - public async Task> DeleteCurrentSession() + public async Task> DeleteCurrentSession() { if (HttpContext.Items["CurrentUser"] is not Account currentUser || - HttpContext.Items["CurrentSession"] is not Session currentSession) return Unauthorized(); + HttpContext.Items["CurrentSession"] is not AuthSession currentSession) return Unauthorized(); try { @@ -537,7 +488,7 @@ public class AccountCurrentController( } [HttpPatch("sessions/{id:guid}/label")] - public async Task> UpdateSessionLabel(Guid id, [FromBody] string label) + public async Task> UpdateSessionLabel(Guid id, [FromBody] string label) { if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); @@ -553,10 +504,10 @@ public class AccountCurrentController( } [HttpPatch("sessions/current/label")] - public async Task> UpdateCurrentSessionLabel([FromBody] string label) + public async Task> UpdateCurrentSessionLabel([FromBody] string label) { if (HttpContext.Items["CurrentUser"] is not Account currentUser || - HttpContext.Items["CurrentSession"] is not Session currentSession) return Unauthorized(); + HttpContext.Items["CurrentSession"] is not AuthSession currentSession) return Unauthorized(); try { @@ -672,9 +623,9 @@ public class AccountCurrentController( } [HttpGet("badges")] - [ProducesResponseType>(StatusCodes.Status200OK)] + [ProducesResponseType>(StatusCodes.Status200OK)] [Authorize] - public async Task>> GetBadges() + public async Task>> GetBadges() { if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); @@ -686,7 +637,7 @@ public class AccountCurrentController( [HttpPost("badges/{id:guid}/active")] [Authorize] - public async Task> ActivateBadge(Guid id) + public async Task> ActivateBadge(Guid id) { if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); diff --git a/DysonNetwork.Pass/Account/AccountEventService.cs b/DysonNetwork.Pass/Account/AccountEventService.cs index b8fdf9d..234b29b 100644 --- a/DysonNetwork.Pass/Account/AccountEventService.cs +++ b/DysonNetwork.Pass/Account/AccountEventService.cs @@ -1,21 +1,16 @@ using System.Globalization; -using DysonNetwork.Pass; -using DysonNetwork.Pass.Connection; -using DysonNetwork.Pass.Storage; using DysonNetwork.Pass.Wallet; +using DysonNetwork.Shared.Cache; using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Caching.Distributed; using Microsoft.Extensions.Localization; using NodaTime; -using Org.BouncyCastle.Asn1.X509; namespace DysonNetwork.Pass.Account; public class AccountEventService( AppDatabase db, - WebSocketService ws, - ICacheService cache, PaymentService payment, + ICacheService cache, IStringLocalizer localizer ) { @@ -34,7 +29,7 @@ public class AccountEventService( var cachedStatus = await cache.GetAsync(cacheKey); if (cachedStatus is not null) { - cachedStatus!.IsOnline = !cachedStatus.IsInvisible && ws.GetAccountIsConnected(userId); + cachedStatus!.IsOnline = !cachedStatus.IsInvisible; // && ws.GetAccountIsConnected(userId); return cachedStatus; } @@ -44,7 +39,7 @@ public class AccountEventService( .Where(e => e.ClearedAt == null || e.ClearedAt > now) .OrderByDescending(e => e.CreatedAt) .FirstOrDefaultAsync(); - var isOnline = ws.GetAccountIsConnected(userId); + var isOnline = false; // TODO: Get connection status if (status is not null) { status.IsOnline = !status.IsInvisible && isOnline; @@ -65,7 +60,7 @@ public class AccountEventService( }; } - return new Status + return new Status { Attitude = StatusAttitude.Neutral, IsOnline = false, @@ -86,7 +81,7 @@ public class AccountEventService( var cachedStatus = await cache.GetAsync(cacheKey); if (cachedStatus != null) { - cachedStatus.IsOnline = !cachedStatus.IsInvisible && ws.GetAccountIsConnected(userId); + cachedStatus.IsOnline = !cachedStatus.IsInvisible /* && ws.GetAccountIsConnected(userId) */; results[userId] = cachedStatus; } else @@ -109,7 +104,7 @@ public class AccountEventService( foreach (var status in statusesFromDb) { - var isOnline = ws.GetAccountIsConnected(status.AccountId); + var isOnline = false; // ws.GetAccountIsConnected(status.AccountId); status.IsOnline = !status.IsInvisible && isOnline; results[status.AccountId] = status; var cacheKey = $"{StatusCacheKey}{status.AccountId}"; @@ -122,7 +117,7 @@ public class AccountEventService( { foreach (var userId in usersWithoutStatus) { - var isOnline = ws.GetAccountIsConnected(userId); + var isOnline = false; // ws.GetAccountIsConnected(userId); var defaultStatus = new Status { Attitude = StatusAttitude.Neutral, @@ -198,11 +193,11 @@ public class AccountEventService( public async Task CheckInDaily(Account user) { var lockKey = $"{CheckInLockKey}{user.Id}"; - + try { var lk = await cache.AcquireLockAsync(lockKey, TimeSpan.FromMinutes(1), TimeSpan.FromMilliseconds(100)); - + if (lk != null) await lk.ReleaseAsync(); } @@ -210,9 +205,10 @@ public class AccountEventService( { // Ignore errors from this pre-check } - + // Now try to acquire the lock properly - await using var lockObj = await cache.AcquireLockAsync(lockKey, TimeSpan.FromMinutes(1), TimeSpan.FromSeconds(5)); + await using var lockObj = + await cache.AcquireLockAsync(lockKey, TimeSpan.FromMinutes(1), TimeSpan.FromSeconds(5)); if (lockObj is null) throw new InvalidOperationException("Check-in was in progress."); var cultureInfo = new CultureInfo(user.Language, false); @@ -274,7 +270,7 @@ public class AccountEventService( s.SetProperty(b => b.Experience, b => b.Experience + result.RewardExperience) ); db.AccountCheckInResults.Add(result); - await db.SaveChangesAsync(); // Don't forget to save changes to the database + await db.SaveChangesAsync(); // Don't forget to save changes to the database // The lock will be automatically released by the await using statement return result; diff --git a/DysonNetwork.Pass/Account/AccountService.cs b/DysonNetwork.Pass/Account/AccountService.cs index ed80166..6ba2919 100644 --- a/DysonNetwork.Pass/Account/AccountService.cs +++ b/DysonNetwork.Pass/Account/AccountService.cs @@ -1,16 +1,14 @@ using System.Globalization; -using DysonNetwork.Pass; +using DysonNetwork.Pass.Auth; using DysonNetwork.Pass.Auth.OpenId; using DysonNetwork.Pass.Email; - using DysonNetwork.Pass.Localization; using DysonNetwork.Pass.Permission; -using DysonNetwork.Pass.Storage; +using DysonNetwork.Shared.Cache; using EFCore.BulkExtensions; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Localization; using NodaTime; -using Org.BouncyCastle.Utilities; using OtpNet; namespace DysonNetwork.Pass.Account; @@ -454,7 +452,7 @@ public class AccountService( ); } - public async Task UpdateSessionLabel(Account account, Guid sessionId, string label) + public async Task UpdateSessionLabel(Account account, Guid sessionId, string label) { var session = await db.AuthSessions .Include(s => s.Challenge) @@ -574,7 +572,7 @@ public class AccountService( /// This method will grant a badge to the account. /// Shouldn't be exposed to normal user and the user itself. /// - public async Task GrantBadge(Account account, Badge badge) + public async Task GrantBadge(Account account, AccountBadge badge) { badge.AccountId = account.Id; db.Badges.Add(badge); diff --git a/DysonNetwork.Pass/Account/ActionLog.cs b/DysonNetwork.Pass/Account/ActionLog.cs index 03a4d02..32f0c3f 100644 --- a/DysonNetwork.Pass/Account/ActionLog.cs +++ b/DysonNetwork.Pass/Account/ActionLog.cs @@ -1,5 +1,6 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; +using DysonNetwork.Shared.Data; using Point = NetTopologySuite.Geometries.Point; namespace DysonNetwork.Pass.Account; diff --git a/DysonNetwork.Pass/Account/ActionLogService.cs b/DysonNetwork.Pass/Account/ActionLogService.cs index a85ff6d..b5ca0b4 100644 --- a/DysonNetwork.Pass/Account/ActionLogService.cs +++ b/DysonNetwork.Pass/Account/ActionLogService.cs @@ -1,7 +1,5 @@ -using Quartz; -using DysonNetwork.Pass; -using DysonNetwork.Pass.Storage; -using DysonNetwork.Pass.Storage.Handlers; +using DysonNetwork.Shared.Cache; +using DysonNetwork.Shared.Geo; namespace DysonNetwork.Pass.Account; @@ -38,7 +36,7 @@ public class ActionLogService(GeoIpService geo, FlushBufferService fbs) else throw new ArgumentException("No user context was found"); - if (request.HttpContext.Items["CurrentSession"] is Auth.Session currentSession) + if (request.HttpContext.Items["CurrentSession"] is Auth.AuthSession currentSession) log.SessionId = currentSession.Id; fbs.Enqueue(log); diff --git a/DysonNetwork.Pass/Account/Badge.cs b/DysonNetwork.Pass/Account/Badge.cs index 7f15899..b8cb8b1 100644 --- a/DysonNetwork.Pass/Account/Badge.cs +++ b/DysonNetwork.Pass/Account/Badge.cs @@ -1,11 +1,12 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using System.Text.Json.Serialization; +using DysonNetwork.Shared.Data; using NodaTime; namespace DysonNetwork.Pass.Account; -public class Badge : ModelBase +public class AccountBadge : ModelBase { public Guid Id { get; set; } = Guid.NewGuid(); [MaxLength(1024)] public string Type { get; set; } = null!; diff --git a/DysonNetwork.Pass/Account/Event.cs b/DysonNetwork.Pass/Account/Event.cs index cbf63f5..ef83d14 100644 --- a/DysonNetwork.Pass/Account/Event.cs +++ b/DysonNetwork.Pass/Account/Event.cs @@ -1,5 +1,6 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; +using DysonNetwork.Shared.Data; using NodaTime; namespace DysonNetwork.Pass.Account; diff --git a/DysonNetwork.Pass/Account/MagicSpell.cs b/DysonNetwork.Pass/Account/MagicSpell.cs index 37f19be..200a33f 100644 --- a/DysonNetwork.Pass/Account/MagicSpell.cs +++ b/DysonNetwork.Pass/Account/MagicSpell.cs @@ -1,6 +1,7 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using System.Text.Json.Serialization; +using DysonNetwork.Shared.Data; using Microsoft.EntityFrameworkCore; using NodaTime; diff --git a/DysonNetwork.Pass/Account/MagicSpellService.cs b/DysonNetwork.Pass/Account/MagicSpellService.cs index 6140c1f..955390e 100644 --- a/DysonNetwork.Pass/Account/MagicSpellService.cs +++ b/DysonNetwork.Pass/Account/MagicSpellService.cs @@ -1,23 +1,18 @@ -using System.Globalization; using System.Security.Cryptography; using System.Text.Json; -using DysonNetwork.Pass; -using DysonNetwork.Pass.Pages.Emails; using DysonNetwork.Pass.Permission; -using DysonNetwork.Pass.Resources.Localization; -using DysonNetwork.Pass.Resources.Pages.Emails; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Localization; using NodaTime; +using EmailResource = DysonNetwork.Pass.Localization.EmailResource; namespace DysonNetwork.Pass.Account; public class MagicSpellService( AppDatabase db, - EmailService email, IConfiguration configuration, ILogger logger, - IStringLocalizer localizer + IStringLocalizer localizer ) { public async Task CreateMagicSpell( @@ -84,61 +79,61 @@ public class MagicSpellService( try { - switch (spell.Type) - { - case MagicSpellType.AccountActivation: - await email.SendTemplatedEmailAsync( - contact.Account.Nick, - contact.Content, - localizer["EmailLandingTitle"], - new LandingEmailModel - { - Name = contact.Account.Name, - Link = link - } - ); - break; - case MagicSpellType.AccountRemoval: - await email.SendTemplatedEmailAsync( - contact.Account.Nick, - contact.Content, - localizer["EmailAccountDeletionTitle"], - new AccountDeletionEmailModel - { - Name = contact.Account.Name, - Link = link - } - ); - break; - case MagicSpellType.AuthPasswordReset: - await email.SendTemplatedEmailAsync( - contact.Account.Nick, - contact.Content, - localizer["EmailAccountDeletionTitle"], - new PasswordResetEmailModel - { - Name = contact.Account.Name, - Link = link - } - ); - break; - case MagicSpellType.ContactVerification: - if (spell.Meta["contact_method"] is not string contactMethod) - throw new InvalidOperationException("Contact method is not found."); - await email.SendTemplatedEmailAsync( - contact.Account.Nick, - contactMethod!, - localizer["EmailContactVerificationTitle"], - new ContactVerificationEmailModel - { - Name = contact.Account.Name, - Link = link - } - ); - break; - default: - throw new ArgumentOutOfRangeException(); - } + // switch (spell.Type) + // { + // case MagicSpellType.AccountActivation: + // await email.SendTemplatedEmailAsync( + // contact.Account.Nick, + // contact.Content, + // localizer["EmailLandingTitle"], + // new LandingEmailModel + // { + // Name = contact.Account.Name, + // Link = link + // } + // ); + // break; + // case MagicSpellType.AccountRemoval: + // await email.SendTemplatedEmailAsync( + // contact.Account.Nick, + // contact.Content, + // localizer["EmailAccountDeletionTitle"], + // new AccountDeletionEmailModel + // { + // Name = contact.Account.Name, + // Link = link + // } + // ); + // break; + // case MagicSpellType.AuthPasswordReset: + // await email.SendTemplatedEmailAsync( + // contact.Account.Nick, + // contact.Content, + // localizer["EmailAccountDeletionTitle"], + // new PasswordResetEmailModel + // { + // Name = contact.Account.Name, + // Link = link + // } + // ); + // break; + // case MagicSpellType.ContactVerification: + // if (spell.Meta["contact_method"] is not string contactMethod) + // throw new InvalidOperationException("Contact method is not found."); + // await email.SendTemplatedEmailAsync( + // contact.Account.Nick, + // contactMethod!, + // localizer["EmailContactVerificationTitle"], + // new ContactVerificationEmailModel + // { + // Name = contact.Account.Name, + // Link = link + // } + // ); + // break; + // default: + // throw new ArgumentOutOfRangeException(); + // } } catch (Exception err) { diff --git a/DysonNetwork.Pass/Account/Notification.cs b/DysonNetwork.Pass/Account/Notification.cs index f2e2c8e..090ed41 100644 --- a/DysonNetwork.Pass/Account/Notification.cs +++ b/DysonNetwork.Pass/Account/Notification.cs @@ -1,6 +1,7 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using System.Text.Json.Serialization; +using DysonNetwork.Shared.Data; using Microsoft.EntityFrameworkCore; using NodaTime; diff --git a/DysonNetwork.Pass/Account/NotificationController.cs b/DysonNetwork.Pass/Account/NotificationController.cs index 8ad4681..ea2cf5f 100644 --- a/DysonNetwork.Pass/Account/NotificationController.cs +++ b/DysonNetwork.Pass/Account/NotificationController.cs @@ -1,5 +1,6 @@ using System.ComponentModel.DataAnnotations; using DysonNetwork.Pass; +using DysonNetwork.Pass.Auth; using DysonNetwork.Pass.Permission; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; @@ -69,7 +70,7 @@ public class NotificationController(AppDatabase db, NotificationService nty) : C HttpContext.Items.TryGetValue("CurrentUser", out var currentUserValue); var currentUser = currentUserValue as Account; if (currentUser == null) return Unauthorized(); - var currentSession = currentSessionValue as Session; + var currentSession = currentSessionValue as AuthSession; if (currentSession == null) return Unauthorized(); var result = @@ -87,7 +88,7 @@ public class NotificationController(AppDatabase db, NotificationService nty) : C HttpContext.Items.TryGetValue("CurrentUser", out var currentUserValue); var currentUser = currentUserValue as Account; if (currentUser == null) return Unauthorized(); - var currentSession = currentSessionValue as Session; + var currentSession = currentSessionValue as AuthSession; if (currentSession == null) return Unauthorized(); var affectedRows = await db.NotificationPushSubscriptions diff --git a/DysonNetwork.Pass/Account/NotificationService.cs b/DysonNetwork.Pass/Account/NotificationService.cs index 7fd0099..202ded6 100644 --- a/DysonNetwork.Pass/Account/NotificationService.cs +++ b/DysonNetwork.Pass/Account/NotificationService.cs @@ -9,9 +9,9 @@ namespace DysonNetwork.Pass.Account; public class NotificationService( AppDatabase db, - WebSocketService ws, IHttpClientFactory httpFactory, - IConfiguration config) + IConfiguration config +) { private readonly string _notifyTopic = config["Notifications:Topic"]!; private readonly Uri _notifyEndpoint = new(config["Notifications:Endpoint"]!); @@ -31,7 +31,7 @@ public class NotificationService( ) { var now = SystemClock.Instance.GetCurrentInstant(); - + // First check if a matching subscription exists var existingSubscription = await db.NotificationPushSubscriptions .Where(s => s.AccountId == account.Id) @@ -110,12 +110,6 @@ public class NotificationService( public async Task DeliveryNotification(Notification notification) { - ws.SendPacketToAccount(notification.AccountId, new WebSocketPacket - { - Type = "notifications.new", - Data = notification - }); - // Pushing the notification var subscribers = await db.NotificationPushSubscriptions .Where(s => s.AccountId == notification.AccountId) @@ -164,11 +158,6 @@ public class NotificationService( { notification.Account = account; notification.AccountId = account.Id; - ws.SendPacketToAccount(account.Id, new WebSocketPacket - { - Type = "notifications.new", - Data = notification - }); } var subscribers = await db.NotificationPushSubscriptions @@ -202,11 +191,6 @@ public class NotificationService( { notification.Account = account; notification.AccountId = account.Id; - ws.SendPacketToAccount(account.Id, new WebSocketPacket - { - Type = "notifications.new", - Data = notification - }); } var accountsId = accounts.Select(x => x.Id).ToList(); diff --git a/DysonNetwork.Pass/Account/Relationship.cs b/DysonNetwork.Pass/Account/Relationship.cs index 7b4aedd..9131b78 100644 --- a/DysonNetwork.Pass/Account/Relationship.cs +++ b/DysonNetwork.Pass/Account/Relationship.cs @@ -1,3 +1,4 @@ +using DysonNetwork.Shared.Data; using NodaTime; namespace DysonNetwork.Pass.Account; diff --git a/DysonNetwork.Pass/AppDatabase.cs b/DysonNetwork.Pass/AppDatabase.cs index 424af99..04b8a39 100644 --- a/DysonNetwork.Pass/AppDatabase.cs +++ b/DysonNetwork.Pass/AppDatabase.cs @@ -3,6 +3,8 @@ using System.Reflection; using DysonNetwork.Pass.Account; using DysonNetwork.Pass.Auth; using DysonNetwork.Pass.Permission; +using DysonNetwork.Pass.Wallet; +using DysonNetwork.Shared.Data; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Design; using Microsoft.EntityFrameworkCore.Query; @@ -11,18 +13,6 @@ using Quartz; namespace DysonNetwork.Pass; -public interface IIdentifiedResource -{ - public string ResourceIdentifier { get; } -} - -public abstract class ModelBase -{ - public Instant CreatedAt { get; set; } - public Instant UpdatedAt { get; set; } - public Instant? DeletedAt { get; set; } -} - public class AppDatabase( DbContextOptions options, IConfiguration configuration @@ -43,12 +33,19 @@ public class AppDatabase( public DbSet AccountCheckInResults { get; set; } public DbSet Notifications { get; set; } public DbSet NotificationPushSubscriptions { get; set; } - public DbSet Badges { get; set; } + public DbSet Badges { get; set; } public DbSet ActionLogs { get; set; } public DbSet AbuseReports { get; set; } - public DbSet AuthSessions { get; set; } - public DbSet AuthChallenges { get; set; } + public DbSet AuthSessions { get; set; } + public DbSet AuthChallenges { get; set; } + + public DbSet Wallets { get; set; } + public DbSet WalletPockets { get; set; } + public DbSet PaymentOrders { get; set; } + public DbSet PaymentTransactions { get; set; } + public DbSet WalletSubscriptions { get; set; } + public DbSet WalletCoupons { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { diff --git a/DysonNetwork.Pass/Auth/Auth.cs b/DysonNetwork.Pass/Auth/Auth.cs index fd8632a..45c4e41 100644 --- a/DysonNetwork.Pass/Auth/Auth.cs +++ b/DysonNetwork.Pass/Auth/Auth.cs @@ -65,7 +65,7 @@ public class DysonTokenAuthHandler( return AuthenticateResult.Fail("Invalid token."); // Try to get session from cache first - var session = await cache.GetAsync($"{AuthCachePrefix}{sessionId}"); + var session = await cache.GetAsync($"{AuthCachePrefix}{sessionId}"); // If not in cache, load from database if (session is null) diff --git a/DysonNetwork.Pass/Auth/AuthController.cs b/DysonNetwork.Pass/Auth/AuthController.cs index 517da48..ab38462 100644 --- a/DysonNetwork.Pass/Auth/AuthController.cs +++ b/DysonNetwork.Pass/Auth/AuthController.cs @@ -27,7 +27,7 @@ public class AuthController( } [HttpPost("challenge")] - public async Task> StartChallenge([FromBody] ChallengeRequest request) + public async Task> StartChallenge([FromBody] ChallengeRequest request) { var account = await accounts.LookupAccount(request.Account); if (account is null) return NotFound("Account was not found."); @@ -47,7 +47,7 @@ public class AuthController( .FirstOrDefaultAsync(); if (existingChallenge is not null) return existingChallenge; - var challenge = new Challenge + var challenge = new AuthChallenge { ExpiredAt = Instant.FromDateTimeUtc(DateTime.UtcNow.AddHours(1)), StepTotal = await auth.DetectChallengeRisk(Request, account), @@ -72,7 +72,7 @@ public class AuthController( } [HttpGet("challenge/{id:guid}")] - public async Task> GetChallenge([FromRoute] Guid id) + public async Task> GetChallenge([FromRoute] Guid id) { var challenge = await db.AuthChallenges .Include(e => e.Account) @@ -132,7 +132,7 @@ public class AuthController( } [HttpPatch("challenge/{id:guid}")] - public async Task> DoChallenge( + public async Task> DoChallenge( [FromRoute] Guid id, [FromBody] PerformChallengeRequest request ) @@ -236,7 +236,7 @@ public class AuthController( if (session is not null) return BadRequest("Session already exists for this challenge."); - session = new Session + session = new AuthSession { LastGrantedAt = Instant.FromDateTimeUtc(DateTime.UtcNow), ExpiredAt = Instant.FromDateTimeUtc(DateTime.UtcNow.AddDays(30)), diff --git a/DysonNetwork.Pass/Auth/AuthService.cs b/DysonNetwork.Pass/Auth/AuthService.cs index 7488b3c..cb35d35 100644 --- a/DysonNetwork.Pass/Auth/AuthService.cs +++ b/DysonNetwork.Pass/Auth/AuthService.cs @@ -73,9 +73,9 @@ public class AuthService( return totalRequiredSteps; } - public async Task CreateSessionForOidcAsync(Account.Account account, Instant time, Guid? customAppId = null) + public async Task CreateSessionForOidcAsync(Account.Account account, Instant time, Guid? customAppId = null) { - var challenge = new Challenge + var challenge = new AuthChallenge { AccountId = account.Id, IpAddress = HttpContext.Connection.RemoteIpAddress?.ToString(), @@ -85,7 +85,7 @@ public class AuthService( Type = customAppId is not null ? ChallengeType.OAuth : ChallengeType.Oidc }; - var session = new Session + var session = new AuthSession { AccountId = account.Id, CreatedAt = time, @@ -154,7 +154,7 @@ public class AuthService( } } - public string CreateToken(Session session) + public string CreateToken(AuthSession session) { // Load the private key for signing var privateKeyPem = File.ReadAllText(config["AuthToken:PrivateKeyPath"]!); @@ -183,7 +183,7 @@ public class AuthService( return $"{payloadBase64}.{signatureBase64}"; } - public async Task ValidateSudoMode(Session session, string? pinCode) + public async Task ValidateSudoMode(AuthSession session, string? pinCode) { // Check if the session is already in sudo mode (cached) var sudoModeKey = $"accounts:{session.Id}:sudo"; diff --git a/DysonNetwork.Pass/Auth/CompactTokenService.cs b/DysonNetwork.Pass/Auth/CompactTokenService.cs index 3ef9742..0c49da0 100644 --- a/DysonNetwork.Pass/Auth/CompactTokenService.cs +++ b/DysonNetwork.Pass/Auth/CompactTokenService.cs @@ -7,7 +7,7 @@ public class CompactTokenService(IConfiguration config) private readonly string _privateKeyPath = config["AuthToken:PrivateKeyPath"] ?? throw new InvalidOperationException("AuthToken:PrivateKeyPath configuration is missing"); - public string CreateToken(Session session) + public string CreateToken(AuthSession session) { // Load the private key for signing var privateKeyPem = File.ReadAllText(_privateKeyPath); diff --git a/DysonNetwork.Pass/Auth/OidcProvider/Controllers/OidcProviderController.cs b/DysonNetwork.Pass/Auth/OidcProvider/Controllers/OidcProviderController.cs index c033b93..e96a007 100644 --- a/DysonNetwork.Pass/Auth/OidcProvider/Controllers/OidcProviderController.cs +++ b/DysonNetwork.Pass/Auth/OidcProvider/Controllers/OidcProviderController.cs @@ -114,7 +114,7 @@ public class OidcProviderController( public async Task GetUserInfo() { if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser || - HttpContext.Items["CurrentSession"] is not Session currentSession) return Unauthorized(); + HttpContext.Items["CurrentSession"] is not AuthSession currentSession) return Unauthorized(); // Get requested scopes from the token var scopes = currentSession.Challenge.Scopes; diff --git a/DysonNetwork.Pass/Auth/OidcProvider/Services/OidcProviderService.cs b/DysonNetwork.Pass/Auth/OidcProvider/Services/OidcProviderService.cs index a4d5968..0c1d86e 100644 --- a/DysonNetwork.Pass/Auth/OidcProvider/Services/OidcProviderService.cs +++ b/DysonNetwork.Pass/Auth/OidcProvider/Services/OidcProviderService.cs @@ -37,7 +37,7 @@ public class OidcProviderService( .FirstOrDefaultAsync(c => c.Id == appId); } - public async Task FindValidSessionAsync(Guid accountId, Guid clientId) + public async Task FindValidSessionAsync(Guid accountId, Guid clientId) { var now = SystemClock.Instance.GetCurrentInstant(); @@ -76,7 +76,7 @@ public class OidcProviderService( if (client == null) throw new InvalidOperationException("Client not found"); - Session session; + AuthSession session; var clock = SystemClock.Instance; var now = clock.GetCurrentInstant(); @@ -126,7 +126,7 @@ public class OidcProviderService( private string GenerateJwtToken( CustomApp client, - Session session, + AuthSession session, Instant expiresAt, IEnumerable? scopes = null ) @@ -199,7 +199,7 @@ public class OidcProviderService( } } - public async Task FindSessionByIdAsync(Guid sessionId) + public async Task FindSessionByIdAsync(Guid sessionId) { return await db.AuthSessions .Include(s => s.Account) @@ -208,7 +208,7 @@ public class OidcProviderService( .FirstOrDefaultAsync(s => s.Id == sessionId); } - private static string GenerateRefreshToken(Session session) + private static string GenerateRefreshToken(AuthSession session) { return Convert.ToBase64String(session.Id.ToByteArray()); } @@ -221,7 +221,7 @@ public class OidcProviderService( } public async Task GenerateAuthorizationCodeForReuseSessionAsync( - Session session, + AuthSession session, Guid clientId, string redirectUri, IEnumerable scopes, diff --git a/DysonNetwork.Pass/Auth/OpenId/OidcController.cs b/DysonNetwork.Pass/Auth/OpenId/OidcController.cs index 43f5053..594675f 100644 --- a/DysonNetwork.Pass/Auth/OpenId/OidcController.cs +++ b/DysonNetwork.Pass/Auth/OpenId/OidcController.cs @@ -68,7 +68,7 @@ public class OidcController( /// Handles Apple authentication directly from mobile apps /// [HttpPost("apple/mobile")] - public async Task> AppleMobileLogin( + public async Task> AppleMobileLogin( [FromBody] AppleMobileSignInRequest request) { try diff --git a/DysonNetwork.Pass/Auth/OpenId/OidcService.cs b/DysonNetwork.Pass/Auth/OpenId/OidcService.cs index ea1b7df..d79ad29 100644 --- a/DysonNetwork.Pass/Auth/OpenId/OidcService.cs +++ b/DysonNetwork.Pass/Auth/OpenId/OidcService.cs @@ -187,7 +187,7 @@ public abstract class OidcService( /// Creates a challenge and session for an authenticated user /// Also creates or updates the account connection /// - public async Task CreateChallengeForUserAsync( + public async Task CreateChallengeForUserAsync( OidcUserInfo userInfo, Account.Account account, HttpContext request, @@ -217,7 +217,7 @@ public abstract class OidcService( // Create a challenge that's already completed var now = SystemClock.Instance.GetCurrentInstant(); - var challenge = new Challenge + var challenge = new AuthChallenge { ExpiredAt = now.Plus(Duration.FromHours(1)), StepTotal = await auth.DetectChallengeRisk(request.Request, account), diff --git a/DysonNetwork.Pass/Auth/Session.cs b/DysonNetwork.Pass/Auth/Session.cs index 4fcfee9..bcd0ced 100644 --- a/DysonNetwork.Pass/Auth/Session.cs +++ b/DysonNetwork.Pass/Auth/Session.cs @@ -2,12 +2,13 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using System.Text.Json.Serialization; using DysonNetwork.Pass; +using DysonNetwork.Shared.Data; using NodaTime; using Point = NetTopologySuite.Geometries.Point; namespace DysonNetwork.Pass.Auth; -public class Session : ModelBase +public class AuthSession : ModelBase { public Guid Id { get; set; } = Guid.NewGuid(); [MaxLength(1024)] public string? Label { get; set; } @@ -17,7 +18,7 @@ public class Session : ModelBase public Guid AccountId { get; set; } [JsonIgnore] public Account.Account Account { get; set; } = null!; public Guid ChallengeId { get; set; } - public Challenge Challenge { get; set; } = null!; + public AuthChallenge Challenge { get; set; } = null!; public Guid? AppId { get; set; } // public CustomApp? App { get; set; } } @@ -40,7 +41,7 @@ public enum ChallengePlatform Linux } -public class Challenge : ModelBase +public class AuthChallenge : ModelBase { public Guid Id { get; set; } = Guid.NewGuid(); public Instant? ExpiredAt { get; set; } @@ -61,7 +62,7 @@ public class Challenge : ModelBase public Guid AccountId { get; set; } [JsonIgnore] public Account.Account Account { get; set; } = null!; - public Challenge Normalize() + public AuthChallenge Normalize() { if (StepRemain == 0 && BlacklistFactors.Count == 0) StepRemain = StepTotal; return this; diff --git a/DysonNetwork.Pass/DysonNetwork.Pass.csproj b/DysonNetwork.Pass/DysonNetwork.Pass.csproj index 6869e9f..35dd6df 100644 --- a/DysonNetwork.Pass/DysonNetwork.Pass.csproj +++ b/DysonNetwork.Pass/DysonNetwork.Pass.csproj @@ -39,4 +39,73 @@ + + + True + True + NotificationResource.resx + + + True + True + SharedResource.resx + + + True + True + NotificationResource.resx + + + True + True + SharedResource.resx + + + True + True + AccountEventResource.resx + + + True + True + AccountEventResource.resx + + + + + + ResXFileCodeGenerator + Email.LandingResource.Designer.cs + + + ResXFileCodeGenerator + NotificationResource.Designer.cs + + + ResXFileCodeGenerator + SharedResource.Designer.cs + + + ResXFileCodeGenerator + AccountEventResource.Designer.cs + + + + + + + + + + + + + + + + + + + + diff --git a/DysonNetwork.Pass/Email/EmailModels.cs b/DysonNetwork.Pass/Email/EmailModels.cs new file mode 100644 index 0000000..39a3075 --- /dev/null +++ b/DysonNetwork.Pass/Email/EmailModels.cs @@ -0,0 +1,31 @@ +namespace DysonNetwork.Pass.Email; + +public class LandingEmailModel +{ + public required string Name { get; set; } + public required string Link { get; set; } +} + +public class AccountDeletionEmailModel +{ + public required string Name { get; set; } + public required string Link { get; set; } +} + +public class PasswordResetEmailModel +{ + public required string Name { get; set; } + public required string Link { get; set; } +} + +public class VerificationEmailModel +{ + public required string Name { get; set; } + public required string Code { get; set; } +} + +public class ContactVerificationEmailModel +{ + public required string Name { get; set; } + public required string Link { get; set; } +} \ No newline at end of file diff --git a/DysonNetwork.Pass/Email/EmailService.cs b/DysonNetwork.Pass/Email/EmailService.cs new file mode 100644 index 0000000..2051cf4 --- /dev/null +++ b/DysonNetwork.Pass/Email/EmailService.cs @@ -0,0 +1,106 @@ +using MailKit.Net.Smtp; +using Microsoft.AspNetCore.Components; +using MimeKit; + +namespace DysonNetwork.Pass.Email; + +public class EmailServiceConfiguration +{ + public string Server { get; set; } = null!; + public int Port { get; set; } + public bool UseSsl { get; set; } + public string Username { get; set; } = null!; + public string Password { get; set; } = null!; + public string FromAddress { get; set; } = null!; + public string FromName { get; set; } = null!; + public string SubjectPrefix { get; set; } = null!; +} + +public class EmailService +{ + private readonly EmailServiceConfiguration _configuration; + private readonly RazorViewRenderer _viewRenderer; + private readonly ILogger _logger; + + public EmailService(IConfiguration configuration, RazorViewRenderer viewRenderer, ILogger logger) + { + var cfg = configuration.GetSection("Email").Get(); + _configuration = cfg ?? throw new ArgumentException("Email service was not configured."); + _viewRenderer = viewRenderer; + _logger = logger; + } + + public async Task SendEmailAsync(string? recipientName, string recipientEmail, string subject, string textBody) + { + await SendEmailAsync(recipientName, recipientEmail, subject, textBody, null); + } + + public async Task SendEmailAsync(string? recipientName, string recipientEmail, string subject, string textBody, + string? htmlBody) + { + subject = $"[{_configuration.SubjectPrefix}] {subject}"; + + var emailMessage = new MimeMessage(); + emailMessage.From.Add(new MailboxAddress(_configuration.FromName, _configuration.FromAddress)); + emailMessage.To.Add(new MailboxAddress(recipientName, recipientEmail)); + emailMessage.Subject = subject; + + var bodyBuilder = new BodyBuilder + { + TextBody = textBody + }; + + if (!string.IsNullOrEmpty(htmlBody)) + bodyBuilder.HtmlBody = htmlBody; + + emailMessage.Body = bodyBuilder.ToMessageBody(); + + using var client = new SmtpClient(); + await client.ConnectAsync(_configuration.Server, _configuration.Port, _configuration.UseSsl); + await client.AuthenticateAsync(_configuration.Username, _configuration.Password); + await client.SendAsync(emailMessage); + await client.DisconnectAsync(true); + } + + private static string _ConvertHtmlToPlainText(string html) + { + // Remove style tags and their contents + html = System.Text.RegularExpressions.Regex.Replace(html, "]*>.*?", "", + System.Text.RegularExpressions.RegexOptions.Singleline); + + // Replace header tags with text + newlines + html = System.Text.RegularExpressions.Regex.Replace(html, "]*>(.*?)", "$1\n\n", + System.Text.RegularExpressions.RegexOptions.IgnoreCase); + + // Replace line breaks + html = html.Replace("
", "\n").Replace("
", "\n").Replace("
", "\n"); + + // Remove all remaining HTML tags + html = System.Text.RegularExpressions.Regex.Replace(html, "<[^>]+>", ""); + + // Decode HTML entities + html = System.Net.WebUtility.HtmlDecode(html); + + // Remove excess whitespace + html = System.Text.RegularExpressions.Regex.Replace(html, @"\s+", " ").Trim(); + + return html; + } + + public async Task SendTemplatedEmailAsync(string? recipientName, string recipientEmail, + string subject, TModel model) + where TComponent : IComponent + { + try + { + var htmlBody = await _viewRenderer.RenderComponentToStringAsync(model); + var fallbackTextBody = _ConvertHtmlToPlainText(htmlBody); + await SendEmailAsync(recipientName, recipientEmail, subject, fallbackTextBody, htmlBody); + } + catch (Exception err) + { + _logger.LogError(err, "Failed to render email template..."); + throw; + } + } +} \ No newline at end of file diff --git a/DysonNetwork.Pass/Email/RazorViewRenderer.cs b/DysonNetwork.Pass/Email/RazorViewRenderer.cs new file mode 100644 index 0000000..57e76cc --- /dev/null +++ b/DysonNetwork.Pass/Email/RazorViewRenderer.cs @@ -0,0 +1,45 @@ +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Web; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.AspNetCore.Mvc.Razor; +using Microsoft.AspNetCore.Mvc.Rendering; +using Microsoft.AspNetCore.Mvc.ViewEngines; +using Microsoft.AspNetCore.Mvc.ViewFeatures; +using RouteData = Microsoft.AspNetCore.Routing.RouteData; + +namespace DysonNetwork.Pass.Email; + +public class RazorViewRenderer( + IServiceProvider serviceProvider, + ILoggerFactory loggerFactory, + ILogger logger +) +{ + public async Task RenderComponentToStringAsync(TModel? model) + where TComponent : IComponent + { + await using var htmlRenderer = new HtmlRenderer(serviceProvider, loggerFactory); + + return await htmlRenderer.Dispatcher.InvokeAsync(async () => + { + try + { + var dictionary = model?.GetType().GetProperties() + .ToDictionary( + prop => prop.Name, + prop => prop.GetValue(model, null) + ) ?? new Dictionary(); + var parameterView = ParameterView.FromDictionary(dictionary); + var output = await htmlRenderer.RenderComponentAsync(parameterView); + return output.ToHtmlString(); + } + catch (Exception ex) + { + logger.LogError(ex, "Error rendering component {ComponentName}", typeof(TComponent).Name); + throw; + } + }); + } +} \ No newline at end of file diff --git a/DysonNetwork.Pass/Handlers/LastActiveFlushHandler.cs b/DysonNetwork.Pass/Handlers/LastActiveFlushHandler.cs index 216a2c9..4d96e99 100644 --- a/DysonNetwork.Pass/Handlers/LastActiveFlushHandler.cs +++ b/DysonNetwork.Pass/Handlers/LastActiveFlushHandler.cs @@ -7,7 +7,7 @@ namespace DysonNetwork.Pass.Handlers; public class LastActiveInfo { - public Auth.Session Session { get; set; } = null!; + public Auth.AuthSession Session { get; set; } = null!; public Account.Account Account { get; set; } = null!; public Instant SeenAt { get; set; } } diff --git a/DysonNetwork.Pass/Localization/AccountEventResource.cs b/DysonNetwork.Pass/Localization/AccountEventResource.cs new file mode 100644 index 0000000..239505e --- /dev/null +++ b/DysonNetwork.Pass/Localization/AccountEventResource.cs @@ -0,0 +1,6 @@ +namespace DysonNetwork.Pass.Localization; + +public class AccountEventResource +{ + +} \ No newline at end of file diff --git a/DysonNetwork.Pass/Localization/EmailResource.cs b/DysonNetwork.Pass/Localization/EmailResource.cs new file mode 100644 index 0000000..2ebcdb4 --- /dev/null +++ b/DysonNetwork.Pass/Localization/EmailResource.cs @@ -0,0 +1,5 @@ +namespace DysonNetwork.Pass.Localization; + +public class EmailResource +{ +} \ No newline at end of file diff --git a/DysonNetwork.Pass/Localization/NotificationResource.cs b/DysonNetwork.Pass/Localization/NotificationResource.cs new file mode 100644 index 0000000..183c09d --- /dev/null +++ b/DysonNetwork.Pass/Localization/NotificationResource.cs @@ -0,0 +1,6 @@ +namespace DysonNetwork.Pass.Localization; + +public class NotificationResource +{ + +} \ No newline at end of file diff --git a/DysonNetwork.Pass/Localization/SharedResource.cs b/DysonNetwork.Pass/Localization/SharedResource.cs new file mode 100644 index 0000000..f21ad84 --- /dev/null +++ b/DysonNetwork.Pass/Localization/SharedResource.cs @@ -0,0 +1,6 @@ +namespace DysonNetwork.Pass.Localization; + +public class SharedResource +{ + +} \ No newline at end of file diff --git a/DysonNetwork.Pass/Pages/Emails/AccountDeletionEmail.razor b/DysonNetwork.Pass/Pages/Emails/AccountDeletionEmail.razor new file mode 100644 index 0000000..2d4f94c --- /dev/null +++ b/DysonNetwork.Pass/Pages/Emails/AccountDeletionEmail.razor @@ -0,0 +1,42 @@ +@using DysonNetwork.Pass.Localization +@using Microsoft.Extensions.Localization + + + + +

@(Localizer["AccountDeletionHeader"])

+

@(Localizer["AccountDeletionPara1"]) @@@Name,

+

@(Localizer["AccountDeletionPara2"])

+

@(Localizer["AccountDeletionPara3"])

+ + + + + + + + + +

@(Localizer["AccountDeletionPara4"])

+ + +
+ +@code { + [Parameter] public required string Name { get; set; } + [Parameter] public required string Link { get; set; } + + [Inject] IStringLocalizer Localizer { get; set; } = null!; +} \ No newline at end of file diff --git a/DysonNetwork.Pass/Pages/Emails/ContactVerificationEmail.razor b/DysonNetwork.Pass/Pages/Emails/ContactVerificationEmail.razor new file mode 100644 index 0000000..1af0c55 --- /dev/null +++ b/DysonNetwork.Pass/Pages/Emails/ContactVerificationEmail.razor @@ -0,0 +1,43 @@ +@using DysonNetwork.Pass.Localization +@using Microsoft.Extensions.Localization +@using EmailResource = DysonNetwork.Pass.Localization.EmailResource + + + + +

@(Localizer["ContactVerificationHeader"])

+

@(Localizer["ContactVerificationPara1"]) @Name,

+

@(Localizer["ContactVerificationPara2"])

+ + + + + + + + + +

@(Localizer["ContactVerificationPara3"])

+

@(Localizer["ContactVerificationPara4"])

+ + +
+ +@code { + [Parameter] public required string Name { get; set; } + [Parameter] public required string Link { get; set; } + + [Inject] IStringLocalizer Localizer { get; set; } = null!; +} \ No newline at end of file diff --git a/DysonNetwork.Pass/Pages/Emails/EmailLayout.razor b/DysonNetwork.Pass/Pages/Emails/EmailLayout.razor new file mode 100644 index 0000000..c3f22ff --- /dev/null +++ b/DysonNetwork.Pass/Pages/Emails/EmailLayout.razor @@ -0,0 +1,337 @@ +@inherits LayoutComponentBase + + + + + + + + + + + + + + + + + + + +@code { + [Parameter] public RenderFragment? ChildContent { get; set; } +} \ No newline at end of file diff --git a/DysonNetwork.Pass/Pages/Emails/LandingEmail.razor b/DysonNetwork.Pass/Pages/Emails/LandingEmail.razor new file mode 100644 index 0000000..df1e79e --- /dev/null +++ b/DysonNetwork.Pass/Pages/Emails/LandingEmail.razor @@ -0,0 +1,43 @@ +@using DysonNetwork.Pass.Localization +@using Microsoft.Extensions.Localization +@using EmailResource = DysonNetwork.Pass.Localization.EmailResource + + + + +

@(Localizer["LandingHeader1"])

+

@(Localizer["LandingPara1"]) @@@Name,

+

@(Localizer["LandingPara2"])

+

@(Localizer["LandingPara3"])

+ + + + + + + + + +

@(Localizer["LandingPara4"])

+ + +
+ +@code { + [Parameter] public required string Name { get; set; } + [Parameter] public required string Link { get; set; } + + [Inject] IStringLocalizer Localizer { get; set; } = null!; +} \ No newline at end of file diff --git a/DysonNetwork.Pass/Pages/Emails/PasswordResetEmail.razor b/DysonNetwork.Pass/Pages/Emails/PasswordResetEmail.razor new file mode 100644 index 0000000..2b867d9 --- /dev/null +++ b/DysonNetwork.Pass/Pages/Emails/PasswordResetEmail.razor @@ -0,0 +1,44 @@ +@using DysonNetwork.Pass.Localization +@using Microsoft.Extensions.Localization +@using EmailResource = DysonNetwork.Pass.Localization.EmailResource + + + + +

@(Localizer["PasswordResetHeader"])

+

@(Localizer["PasswordResetPara1"]) @@@Name,

+

@(Localizer["PasswordResetPara2"])

+

@(Localizer["PasswordResetPara3"])

+ + + + + + + + + +

@(Localizer["PasswordResetPara4"])

+ + +
+ +@code { + [Parameter] public required string Name { get; set; } + [Parameter] public required string Link { get; set; } + + [Inject] IStringLocalizer Localizer { get; set; } = null!; + [Inject] IStringLocalizer LocalizerShared { get; set; } = null!; +} \ No newline at end of file diff --git a/DysonNetwork.Pass/Pages/Emails/VerificationEmail.razor b/DysonNetwork.Pass/Pages/Emails/VerificationEmail.razor new file mode 100644 index 0000000..2763b19 --- /dev/null +++ b/DysonNetwork.Pass/Pages/Emails/VerificationEmail.razor @@ -0,0 +1,27 @@ +@using DysonNetwork.Pass.Localization +@using Microsoft.Extensions.Localization +@using EmailResource = DysonNetwork.Pass.Localization.EmailResource + + + + +

@(Localizer["VerificationHeader1"])

+

@(Localizer["VerificationPara1"]) @@@Name,

+

@(Localizer["VerificationPara2"])

+

@(Localizer["VerificationPara3"])

+ +

@Code

+ +

@(Localizer["VerificationPara4"])

+

@(Localizer["VerificationPara5"])

+ + +
+ +@code { + [Parameter] public required string Name { get; set; } + [Parameter] public required string Code { get; set; } + + [Inject] IStringLocalizer Localizer { get; set; } = null!; + [Inject] IStringLocalizer LocalizerShared { get; set; } = null!; +} \ No newline at end of file diff --git a/DysonNetwork.Pass/Permission/Permission.cs b/DysonNetwork.Pass/Permission/Permission.cs index 8a0eb4a..531cc7c 100644 --- a/DysonNetwork.Pass/Permission/Permission.cs +++ b/DysonNetwork.Pass/Permission/Permission.cs @@ -2,6 +2,7 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using System.Text.Json; using System.Text.Json.Serialization; +using DysonNetwork.Shared.Data; using Microsoft.EntityFrameworkCore; using NodaTime; diff --git a/DysonNetwork.Pass/Resources/Localization/AccountEventResource.Designer.cs b/DysonNetwork.Pass/Resources/Localization/AccountEventResource.Designer.cs new file mode 100644 index 0000000..c9ec073 --- /dev/null +++ b/DysonNetwork.Pass/Resources/Localization/AccountEventResource.Designer.cs @@ -0,0 +1,222 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace DysonNetwork.Pass.Resources { + using System; + + + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class AccountEventResource { + + private static System.Resources.ResourceManager resourceMan; + + private static System.Globalization.CultureInfo resourceCulture; + + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal AccountEventResource() { + } + + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] + internal static System.Resources.ResourceManager ResourceManager { + get { + if (object.Equals(null, resourceMan)) { + System.Resources.ResourceManager temp = new System.Resources.ResourceManager("DysonNetwork.Pass.Resources.AccountEventResource", typeof(AccountEventResource).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] + internal static System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + internal static string FortuneTipPositiveTitle_1 { + get { + return ResourceManager.GetString("FortuneTipPositiveTitle_1", resourceCulture); + } + } + + internal static string FortuneTipPositiveContent_1 { + get { + return ResourceManager.GetString("FortuneTipPositiveContent_1", resourceCulture); + } + } + + internal static string FortuneTipNegativeTitle_1 { + get { + return ResourceManager.GetString("FortuneTipNegativeTitle_1", resourceCulture); + } + } + + internal static string FortuneTipNegativeContent_1 { + get { + return ResourceManager.GetString("FortuneTipNegativeContent_1", resourceCulture); + } + } + + internal static string FortuneTipPositiveTitle_2 { + get { + return ResourceManager.GetString("FortuneTipPositiveTitle_2", resourceCulture); + } + } + + internal static string FortuneTipPositiveContent_2 { + get { + return ResourceManager.GetString("FortuneTipPositiveContent_2", resourceCulture); + } + } + + internal static string FortuneTipNegativeTitle_2 { + get { + return ResourceManager.GetString("FortuneTipNegativeTitle_2", resourceCulture); + } + } + + internal static string FortuneTipNegativeContent_2 { + get { + return ResourceManager.GetString("FortuneTipNegativeContent_2", resourceCulture); + } + } + + internal static string FortuneTipPositiveTitle_3 { + get { + return ResourceManager.GetString("FortuneTipPositiveTitle_3", resourceCulture); + } + } + + internal static string FortuneTipPositiveContent_3 { + get { + return ResourceManager.GetString("FortuneTipPositiveContent_3", resourceCulture); + } + } + + internal static string FortuneTipNegativeTitle_3 { + get { + return ResourceManager.GetString("FortuneTipNegativeTitle_3", resourceCulture); + } + } + + internal static string FortuneTipNegativeContent_3 { + get { + return ResourceManager.GetString("FortuneTipNegativeContent_3", resourceCulture); + } + } + + internal static string FortuneTipPositiveTitle_4 { + get { + return ResourceManager.GetString("FortuneTipPositiveTitle_4", resourceCulture); + } + } + + internal static string FortuneTipPositiveContent_4 { + get { + return ResourceManager.GetString("FortuneTipPositiveContent_4", resourceCulture); + } + } + + internal static string FortuneTipNegativeTitle_4 { + get { + return ResourceManager.GetString("FortuneTipNegativeTitle_4", resourceCulture); + } + } + + internal static string FortuneTipNegativeContent_4 { + get { + return ResourceManager.GetString("FortuneTipNegativeContent_4", resourceCulture); + } + } + + internal static string FortuneTipPositiveTitle_5 { + get { + return ResourceManager.GetString("FortuneTipPositiveTitle_5", resourceCulture); + } + } + + internal static string FortuneTipPositiveContent_5 { + get { + return ResourceManager.GetString("FortuneTipPositiveContent_5", resourceCulture); + } + } + + internal static string FortuneTipNegativeTitle_5 { + get { + return ResourceManager.GetString("FortuneTipNegativeTitle_5", resourceCulture); + } + } + + internal static string FortuneTipNegativeContent_5 { + get { + return ResourceManager.GetString("FortuneTipNegativeContent_5", resourceCulture); + } + } + + internal static string FortuneTipPositiveTitle_6 { + get { + return ResourceManager.GetString("FortuneTipPositiveTitle_6", resourceCulture); + } + } + + internal static string FortuneTipPositiveContent_6 { + get { + return ResourceManager.GetString("FortuneTipPositiveContent_6", resourceCulture); + } + } + + internal static string FortuneTipNegativeTitle_6 { + get { + return ResourceManager.GetString("FortuneTipNegativeTitle_6", resourceCulture); + } + } + + internal static string FortuneTipNegativeContent_6 { + get { + return ResourceManager.GetString("FortuneTipNegativeContent_6", resourceCulture); + } + } + + internal static string FortuneTipPositiveTitle_7 { + get { + return ResourceManager.GetString("FortuneTipPositiveTitle_7", resourceCulture); + } + } + + internal static string FortuneTipPositiveContent_7 { + get { + return ResourceManager.GetString("FortuneTipPositiveContent_7", resourceCulture); + } + } + + internal static string FortuneTipNegativeTitle_7 { + get { + return ResourceManager.GetString("FortuneTipNegativeTitle_7", resourceCulture); + } + } + + internal static string FortuneTipNegativeContent_7 { + get { + return ResourceManager.GetString("FortuneTipNegativeContent_7", resourceCulture); + } + } + + internal static string FortuneTipNegativeTitle_1_ { + get { + return ResourceManager.GetString("FortuneTipNegativeTitle_1 ", resourceCulture); + } + } + } +} diff --git a/DysonNetwork.Pass/Resources/Localization/AccountEventResource.resx b/DysonNetwork.Pass/Resources/Localization/AccountEventResource.resx new file mode 100644 index 0000000..d6e7dc1 --- /dev/null +++ b/DysonNetwork.Pass/Resources/Localization/AccountEventResource.resx @@ -0,0 +1,113 @@ + + + + + + + + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, + PublicKeyToken=b77a5c561934e089 + + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, + PublicKeyToken=b77a5c561934e089 + + + + Gacha + + + Golden every pull + + + Gacha + + + Won't pull the card you like + + + Gaming + + + Rank up like a hot knife through butter + + + Gaming + + + Dropping ranks like a landslide + + + Lottery + + + Blessed with luck + + + Lottery + + + Ten pulls, all silence + + + Speech + + + Words flow like gems + + + Speech + + + Be careful what you're saying + + + Drawing + + + Inspiration gushes like a spring + + + Drawing + + + Every stroke weighs a thousand pounds + + + Coding + + + 0 error(s), 0 warning(s) + + + Coding + + + 114 error(s), 514 warning(s) + + + Shopping + + + Exchange rate at its lowest + + + Unboxing + + + 225% tariff + + + Gacha + + \ No newline at end of file diff --git a/DysonNetwork.Pass/Resources/Localization/AccountEventResource.zh-hans.resx b/DysonNetwork.Pass/Resources/Localization/AccountEventResource.zh-hans.resx new file mode 100644 index 0000000..a9bd551 --- /dev/null +++ b/DysonNetwork.Pass/Resources/Localization/AccountEventResource.zh-hans.resx @@ -0,0 +1,98 @@ + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 抽卡 + + + 次次出金 + + + 吃大保底 + + + 游戏 + + + 升段如破竹 + + + 游戏 + + + 掉分如山崩 + + + 抽奖 + + + 欧气加身 + + + 抽奖 + + + 十连皆寂 + + + 演讲 + + + 妙语连珠 + + + 演讲 + + + 谨言慎行 + + + 绘图 + + + 灵感如泉涌 + + + 绘图 + + + 下笔如千斤 + + + 编程 + + + 0 error(s), 0 warning(s) + + + 编程 + + + 114 error(s), 514 warning(s) + + + 购物 + + + 汇率低谷 + + + 开箱 + + + 225% 关税 + + + 抽卡 + + \ No newline at end of file diff --git a/DysonNetwork.Pass/Resources/Localization/EmailResource.Designer.cs b/DysonNetwork.Pass/Resources/Localization/EmailResource.Designer.cs new file mode 100644 index 0000000..6bd42d2 --- /dev/null +++ b/DysonNetwork.Pass/Resources/Localization/EmailResource.Designer.cs @@ -0,0 +1,90 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace DysonNetwork.Pass.Resources.Pages.Emails { + using System; + + + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class EmailResource { + + private static System.Resources.ResourceManager resourceMan; + + private static System.Globalization.CultureInfo resourceCulture; + + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal EmailResource() { + } + + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] + internal static System.Resources.ResourceManager ResourceManager { + get { + if (object.Equals(null, resourceMan)) { + System.Resources.ResourceManager temp = new System.Resources.ResourceManager("DysonNetwork.Pass.Resources.Localization.EmailResource", typeof(EmailResource).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] + internal static System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + internal static string LandingHeader1 { + get { + return ResourceManager.GetString("LandingHeader1", resourceCulture); + } + } + + internal static string LandingPara1 { + get { + return ResourceManager.GetString("LandingPara1", resourceCulture); + } + } + + internal static string LandingPara2 { + get { + return ResourceManager.GetString("LandingPara2", resourceCulture); + } + } + + internal static string LandingPara3 { + get { + return ResourceManager.GetString("LandingPara3", resourceCulture); + } + } + + internal static string LandingButton1 { + get { + return ResourceManager.GetString("LandingButton1", resourceCulture); + } + } + + internal static string LandingPara4 { + get { + return ResourceManager.GetString("LandingPara4", resourceCulture); + } + } + + internal static string EmailLandingTitle { + get { + return ResourceManager.GetString("EmailLandingTitle", resourceCulture); + } + } + } +} diff --git a/DysonNetwork.Pass/Resources/Localization/EmailResource.resx b/DysonNetwork.Pass/Resources/Localization/EmailResource.resx new file mode 100644 index 0000000..89dd75c --- /dev/null +++ b/DysonNetwork.Pass/Resources/Localization/EmailResource.resx @@ -0,0 +1,126 @@ + + + + + + + + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Welcome to the Solar Network! + + + Dear, + + + Thank you for creating an account on the Solar Network. We're excited to have you join our community! + + + To access all features and ensure the security of your account, please confirm your registration by clicking the button below: + + + Confirm Registration + + + If you didn't create this account, please ignore this email. + + + Confirm your registration + + + Account Deletion Confirmation + + + Dear, + + + We've received a request to delete your Solar Network account. We're sorry to see you go. + + + To confirm your account deletion, please click the button below. Please note that this action is permanent and cannot be undone. + + + Confirm Account Deletion + + + If you did not request to delete your account, please ignore this email or contact our support team immediately. + + + Confirm your account deletion + + + Reset your password + + + Reset Password + + + Password Reset Request + + + Dear, + + + We recieved a request to reset your Solar Network account password. + + + You can click the button below to continue reset your password. + + + If you didn't request this, you can ignore this email safety. + + + Verify Your Email Address + + + Dear, + + + Thank you for creating an account on the Solar Network. We're excited to have you join our community! + + + To verify your email address and access all features of your account, please use the verification code below: + + + This code will expire in 30 minutes. Please enter it on the verification page to complete your registration. + + + If you didn't create this account, please ignore this email. + + + Verify your email address + + + Verify Your Contact Information + + + Dear, + + + Thank you for updating your contact information on the Solar Network. To ensure your account security, we need to verify this change. + + + Please click the button below to verify your contact information: + + + Verify Contact Information + + + If you didn't request this change, please contact our support team immediately. + + + Verify your contact information + + \ No newline at end of file diff --git a/DysonNetwork.Pass/Resources/Localization/EmailResource.zh-hans.resx b/DysonNetwork.Pass/Resources/Localization/EmailResource.zh-hans.resx new file mode 100644 index 0000000..fc4ed68 --- /dev/null +++ b/DysonNetwork.Pass/Resources/Localization/EmailResource.zh-hans.resx @@ -0,0 +1,119 @@ + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 欢迎来到 Solar Network! + + + 尊敬的 + + + 确认注册 + + + 感谢你在 Solar Network 上注册帐号,我们很激动你即将加入我们的社区! + + + 点击下方按钮来确认你的注册以获得所有功能的权限。 + + + 如果你并没有注册帐号,你可以忽略此邮件。 + + + 确认你的注册 + + + 账户删除确认 + + + 尊敬的 + + + 我们收到了删除您 Solar Network 账户的请求。我们很遗憾看到您的离开。 + + + 请点击下方按钮确认删除您的账户。请注意,此操作是永久性的,无法撤销。 + + + 确认删除账户 + + + 如果您并未请求删除账户,请忽略此邮件或立即联系我们的支持团队。 + + + 确认删除您的账户 + + + 密码重置请求 + + + 尊敬的 + + + 我们收到了重置您 Solar Network 账户密码的请求。 + + + 请点击下方按钮重置您的密码。此链接将在24小时后失效。 + + + 重置密码 + + + 如果您并未请求重置密码,你可以安全地忽略此邮件。 + + + 重置您的密码 + + + 验证您的电子邮箱 + + + 尊敬的 + + + 感谢您在 Solar Network 上注册账号,我们很高兴您即将加入我们的社区! + + + 请使用以下验证码来验证您的电子邮箱并获取账号的所有功能: + + + 此验证码将在30分钟后失效。请在验证页面输入此验证码以完成注册。 + + + 如果您并未创建此账号,请忽略此邮件。 + + + 验证您的电子邮箱 + + + 验证您的联系信息 + + + 尊敬的 + + + 感谢您更新 Solar Network 上的联系信息。为确保您的账户安全,我们需要验证此更改。 + + + 请点击下方按钮验证您的联系信息: + + + 验证联系信息 + + + 如果您没有请求此更改,请立即联系我们的支持团队。 + + + 验证您的联系信息 + + \ No newline at end of file diff --git a/DysonNetwork.Pass/Resources/Localization/NotificationResource.Designer.cs b/DysonNetwork.Pass/Resources/Localization/NotificationResource.Designer.cs new file mode 100644 index 0000000..a095af1 --- /dev/null +++ b/DysonNetwork.Pass/Resources/Localization/NotificationResource.Designer.cs @@ -0,0 +1,162 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace DysonNetwork.Pass.Resources.Localization { + using System; + + + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class NotificationResource { + + private static System.Resources.ResourceManager resourceMan; + + private static System.Globalization.CultureInfo resourceCulture; + + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal NotificationResource() { + } + + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] + internal static System.Resources.ResourceManager ResourceManager { + get { + if (object.Equals(null, resourceMan)) { + System.Resources.ResourceManager temp = new System.Resources.ResourceManager("DysonNetwork.Pass.Resources.Localization.NotificationResource", typeof(NotificationResource).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] + internal static System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + internal static string ChatInviteTitle { + get { + return ResourceManager.GetString("ChatInviteTitle", resourceCulture); + } + } + + internal static string ChatInviteBody { + get { + return ResourceManager.GetString("ChatInviteBody", resourceCulture); + } + } + + internal static string ChatInviteDirectBody { + get { + return ResourceManager.GetString("ChatInviteDirectBody", resourceCulture); + } + } + + internal static string RealmInviteTitle { + get { + return ResourceManager.GetString("RealmInviteTitle", resourceCulture); + } + } + + internal static string RealmInviteBody { + get { + return ResourceManager.GetString("RealmInviteBody", resourceCulture); + } + } + + internal static string PostSubscriptionTitle { + get { + return ResourceManager.GetString("PostSubscriptionTitle", resourceCulture); + } + } + + internal static string PostReactTitle { + get { + return ResourceManager.GetString("PostReactTitle", resourceCulture); + } + } + + internal static string PostReactBody { + get { + return ResourceManager.GetString("PostReactBody", resourceCulture); + } + } + + internal static string PostReactContentBody { + get { + return ResourceManager.GetString("PostReactContentBody", resourceCulture); + } + } + + internal static string PostReplyTitle { + get { + return ResourceManager.GetString("PostReplyTitle", resourceCulture); + } + } + + internal static string PostReplyBody { + get { + return ResourceManager.GetString("PostReplyBody", resourceCulture); + } + } + + internal static string PostReplyContentBody { + get { + return ResourceManager.GetString("PostReplyContentBody", resourceCulture); + } + } + + internal static string PostOnlyMedia { + get { + return ResourceManager.GetString("PostOnlyMedia", resourceCulture); + } + } + + internal static string AuthCodeTitle { + get { + return ResourceManager.GetString("AuthCodeTitle", resourceCulture); + } + } + + internal static string AuthCodeBody { + get { + return ResourceManager.GetString("AuthCodeBody", resourceCulture); + } + } + + internal static string SubscriptionAppliedTitle { + get { + return ResourceManager.GetString("SubscriptionAppliedTitle", resourceCulture); + } + } + + internal static string SubscriptionAppliedBody { + get { + return ResourceManager.GetString("SubscriptionAppliedBody", resourceCulture); + } + } + + internal static string OrderPaidTitle { + get { + return ResourceManager.GetString("OrderPaidTitle", resourceCulture); + } + } + + internal static string OrderPaidBody { + get { + return ResourceManager.GetString("OrderPaidBody", resourceCulture); + } + } + } +} diff --git a/DysonNetwork.Pass/Resources/Localization/NotificationResource.resx b/DysonNetwork.Pass/Resources/Localization/NotificationResource.resx new file mode 100644 index 0000000..283612a --- /dev/null +++ b/DysonNetwork.Pass/Resources/Localization/NotificationResource.resx @@ -0,0 +1,83 @@ + + + + + + + + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, + PublicKeyToken=b77a5c561934e089 + + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, + PublicKeyToken=b77a5c561934e089 + + + + New Chat Invitation + + + You just got invited to join {0} + + + {0} sent an direct message invitation to you + + + New Realm Invitation + + + You just got invited to join {0} + + + {0} just posted {1} + + + {0} reacted your post + + + {0} added a reaction {1} to your post + + + {0} added a reaction {1} to your post {2} + + + {0} replied your post + + + {0} replied: {1} + + + {0} replied post {1}: {2} + + + shared media + + + Disposable Verification Code + + + {0} is your disposable code, it will expires in 5 minutes + + + Subscription {0} just activated for your account + + + Thank for supporting the Solar Network! Your {0} days {1} subscription just begun, feel free to explore the newly unlocked features! + + + Order {0} recipent + + + {0} {1} was removed from your wallet to pay {2} + + \ No newline at end of file diff --git a/DysonNetwork.Pass/Resources/Localization/NotificationResource.zh-hans.resx b/DysonNetwork.Pass/Resources/Localization/NotificationResource.zh-hans.resx new file mode 100644 index 0000000..4cf16b1 --- /dev/null +++ b/DysonNetwork.Pass/Resources/Localization/NotificationResource.zh-hans.resx @@ -0,0 +1,75 @@ + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, + PublicKeyToken=b77a5c561934e089 + + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, + PublicKeyToken=b77a5c561934e089 + + + + 新聊天邀请 + + + 你刚被邀请加入聊天 {} + + + {0} 向你发送了一个私聊邀请 + + + 新加入领域邀请 + + + 你刚被邀请加入领域 {0} + + + {0} 有新帖子 + + + {0} 反应了你的帖子 + + + {0} 给你的帖子添加了一个 {1} 的反应 + + + {0} 给你的帖子添加了一个 {1} 的反应 {2} + + + {0} 回复了你的帖子 + + + {0}:{1} + + + {0} 回复了帖子 {1}: {2} + + + 分享媒体 + + + 一次性验证码 + + + {0} 是你的一次性验证码,它将会在五分钟内过期 + + + {0} 的订阅激活成功 + + + 感谢你支持 Solar Network 的开发!你的 {0} 天 {1} 订阅刚刚开始,接下来来探索新解锁的新功能吧! + + + 订单回执 {0} + + + {0} {1} 已从你的帐户中扣除来支付 {2} + + \ No newline at end of file diff --git a/DysonNetwork.Pass/Resources/Localization/SharedResource.Designer.cs b/DysonNetwork.Pass/Resources/Localization/SharedResource.Designer.cs new file mode 100644 index 0000000..4a303e1 --- /dev/null +++ b/DysonNetwork.Pass/Resources/Localization/SharedResource.Designer.cs @@ -0,0 +1,48 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace DysonNetwork.Pass.Resources { + using System; + + + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class SharedResource { + + private static System.Resources.ResourceManager resourceMan; + + private static System.Globalization.CultureInfo resourceCulture; + + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal SharedResource() { + } + + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] + internal static System.Resources.ResourceManager ResourceManager { + get { + if (object.Equals(null, resourceMan)) { + System.Resources.ResourceManager temp = new System.Resources.ResourceManager("DysonNetwork.Pass.Resources.Localization.SharedResource", typeof(SharedResource).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] + internal static System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + } +} diff --git a/DysonNetwork.Pass/Resources/Localization/SharedResource.resx b/DysonNetwork.Pass/Resources/Localization/SharedResource.resx new file mode 100644 index 0000000..a4c5284 --- /dev/null +++ b/DysonNetwork.Pass/Resources/Localization/SharedResource.resx @@ -0,0 +1,21 @@ + + + + + + + + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/DysonNetwork.Pass/Resources/Localization/SharedResource.zh-hans.resx b/DysonNetwork.Pass/Resources/Localization/SharedResource.zh-hans.resx new file mode 100644 index 0000000..0db1973 --- /dev/null +++ b/DysonNetwork.Pass/Resources/Localization/SharedResource.zh-hans.resx @@ -0,0 +1,14 @@ + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/DysonNetwork.Pass/Wallet/OrderController.cs b/DysonNetwork.Pass/Wallet/OrderController.cs new file mode 100644 index 0000000..bbca4a9 --- /dev/null +++ b/DysonNetwork.Pass/Wallet/OrderController.cs @@ -0,0 +1,57 @@ +using DysonNetwork.Pass.Auth; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; + +namespace DysonNetwork.Pass.Wallet; + +[ApiController] +[Route("/api/orders")] +public class OrderController(PaymentService payment, AuthService auth, AppDatabase db) : ControllerBase +{ + [HttpGet("{id:guid}")] + public async Task> GetOrderById(Guid id) + { + var order = await db.PaymentOrders.FindAsync(id); + + if (order == null) + { + return NotFound(); + } + + return Ok(order); + } + + [HttpPost("{id:guid}/pay")] + [Authorize] + public async Task> PayOrder(Guid id, [FromBody] PayOrderRequest request) + { + if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser || + HttpContext.Items["CurrentSession"] is not AuthSession currentSession) return Unauthorized(); + + // Validate PIN code + if (!await auth.ValidatePinCode(currentUser.Id, request.PinCode)) + return StatusCode(403, "Invalid PIN Code"); + + try + { + // Get the wallet for the current user + var wallet = await db.Wallets.FirstOrDefaultAsync(w => w.AccountId == currentUser.Id); + if (wallet == null) + return BadRequest("Wallet was not found."); + + // Pay the order + var paidOrder = await payment.PayOrderAsync(id, wallet.Id); + return Ok(paidOrder); + } + catch (InvalidOperationException ex) + { + return BadRequest(new { error = ex.Message }); + } + } +} + +public class PayOrderRequest +{ + public string PinCode { get; set; } = string.Empty; +} \ No newline at end of file diff --git a/DysonNetwork.Pass/Wallet/Payment.cs b/DysonNetwork.Pass/Wallet/Payment.cs new file mode 100644 index 0000000..9bd27e1 --- /dev/null +++ b/DysonNetwork.Pass/Wallet/Payment.cs @@ -0,0 +1,61 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using DysonNetwork.Shared.Data; +using NodaTime; + +namespace DysonNetwork.Pass.Wallet; + +public class WalletCurrency +{ + public const string SourcePoint = "points"; + public const string GoldenPoint = "golds"; +} + +public enum OrderStatus +{ + Unpaid, + Paid, + Cancelled, + Finished, + Expired +} + +public class Order : ModelBase +{ + public Guid Id { get; set; } = Guid.NewGuid(); + public OrderStatus Status { get; set; } = OrderStatus.Unpaid; + [MaxLength(128)] public string Currency { get; set; } = null!; + [MaxLength(4096)] public string? Remarks { get; set; } + [MaxLength(4096)] public string? AppIdentifier { get; set; } + [Column(TypeName = "jsonb")] public Dictionary? Meta { get; set; } + public decimal Amount { get; set; } + public Instant ExpiredAt { get; set; } + + public Guid? PayeeWalletId { get; set; } + public Wallet? PayeeWallet { get; set; } = null!; + public Guid? TransactionId { get; set; } + public Transaction? Transaction { get; set; } +} + +public enum TransactionType +{ + System, + Transfer, + Order +} + +public class Transaction : ModelBase +{ + public Guid Id { get; set; } = Guid.NewGuid(); + [MaxLength(128)] public string Currency { get; set; } = null!; + public decimal Amount { get; set; } + [MaxLength(4096)] public string? Remarks { get; set; } + public TransactionType Type { get; set; } + + // When the payer is null, it's pay from the system + public Guid? PayerWalletId { get; set; } + public Wallet? PayerWallet { get; set; } + // When the payee is null, it's pay for the system + public Guid? PayeeWalletId { get; set; } + public Wallet? PayeeWallet { get; set; } +} \ No newline at end of file diff --git a/DysonNetwork.Pass/Wallet/PaymentHandlers/AfdianPaymentHandler.cs b/DysonNetwork.Pass/Wallet/PaymentHandlers/AfdianPaymentHandler.cs new file mode 100644 index 0000000..5390422 --- /dev/null +++ b/DysonNetwork.Pass/Wallet/PaymentHandlers/AfdianPaymentHandler.cs @@ -0,0 +1,446 @@ +using System.Security.Cryptography; +using System.Text; +using System.Text.Json; +using System.Text.Json.Serialization; +using Microsoft.EntityFrameworkCore; +using NodaTime; + +namespace DysonNetwork.Pass.Wallet.PaymentHandlers; + +public class AfdianPaymentHandler( + IHttpClientFactory httpClientFactory, + ILogger logger, + IConfiguration configuration +) +{ + private readonly IHttpClientFactory _httpClientFactory = + httpClientFactory ?? throw new ArgumentNullException(nameof(httpClientFactory)); + + private readonly ILogger _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + + private readonly IConfiguration _configuration = + configuration ?? throw new ArgumentNullException(nameof(configuration)); + + private static readonly JsonSerializerOptions JsonOptions = new() + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + PropertyNameCaseInsensitive = true + }; + + private string CalculateSign(string token, string userId, string paramsJson, long ts) + { + var kvString = $"{token}params{paramsJson}ts{ts}user_id{userId}"; + using (var md5 = MD5.Create()) + { + var hashBytes = md5.ComputeHash(Encoding.UTF8.GetBytes(kvString)); + return BitConverter.ToString(hashBytes).Replace("-", "").ToLower(); + } + } + + public async Task ListOrderAsync(int page = 1) + { + try + { + var token = _configuration["Payment:Auth:Afdian"] ?? "_:_"; + var tokenParts = token.Split(':'); + var userId = tokenParts[0]; + token = tokenParts[1]; + var paramsJson = JsonSerializer.Serialize(new { page }, JsonOptions); + var ts = (long)(DateTime.UtcNow - new DateTime(1970, 1, 1)) + .TotalSeconds; // Current timestamp in seconds + + var sign = CalculateSign(token, userId, paramsJson, ts); + + var client = _httpClientFactory.CreateClient(); + var request = new HttpRequestMessage(HttpMethod.Post, "https://afdian.com/api/open/query-order") + { + Content = new StringContent(JsonSerializer.Serialize(new + { + user_id = userId, + @params = paramsJson, + ts, + sign + }, JsonOptions), Encoding.UTF8, "application/json") + }; + + var response = await client.SendAsync(request); + if (!response.IsSuccessStatusCode) + { + _logger.LogError( + $"Response Error: {response.StatusCode}, {await response.Content.ReadAsStringAsync()}"); + return null; + } + + var result = await JsonSerializer.DeserializeAsync( + await response.Content.ReadAsStreamAsync(), JsonOptions); + return result; + } + catch (Exception ex) + { + _logger.LogError(ex, "Error fetching orders"); + throw; + } + } + + /// + /// Get a specific order by its ID (out_trade_no) + /// + /// The order ID to query + /// The order item if found, otherwise null + public async Task GetOrderAsync(string orderId) + { + if (string.IsNullOrEmpty(orderId)) + { + _logger.LogWarning("Order ID cannot be null or empty"); + return null; + } + + try + { + var token = _configuration["Payment:Auth:Afdian"] ?? "_:_"; + var tokenParts = token.Split(':'); + var userId = tokenParts[0]; + token = tokenParts[1]; + var paramsJson = JsonSerializer.Serialize(new { out_trade_no = orderId }, JsonOptions); + var ts = (long)(DateTime.UtcNow - new DateTime(1970, 1, 1)) + .TotalSeconds; // Current timestamp in seconds + + var sign = CalculateSign(token, userId, paramsJson, ts); + + var client = _httpClientFactory.CreateClient(); + var request = new HttpRequestMessage(HttpMethod.Post, "https://afdian.com/api/open/query-order") + { + Content = new StringContent(JsonSerializer.Serialize(new + { + user_id = userId, + @params = paramsJson, + ts, + sign + }, JsonOptions), Encoding.UTF8, "application/json") + }; + + var response = await client.SendAsync(request); + if (!response.IsSuccessStatusCode) + { + _logger.LogError( + $"Response Error: {response.StatusCode}, {await response.Content.ReadAsStringAsync()}"); + return null; + } + + var result = await JsonSerializer.DeserializeAsync( + await response.Content.ReadAsStreamAsync(), JsonOptions); + + // Check if we have a valid response and orders in the list + if (result?.Data.Orders == null || result.Data.Orders.Count == 0) + { + _logger.LogWarning($"No order found with ID: {orderId}"); + return null; + } + + // Since we're querying by a specific order ID, we should only get one result + return result.Data.Orders.FirstOrDefault(); + } + catch (Exception ex) + { + _logger.LogError(ex, $"Error fetching order with ID: {orderId}"); + throw; + } + } + + /// + /// Get multiple orders by their IDs (out_trade_no) + /// + /// A collection of order IDs to query + /// A list of found order items + public async Task> GetOrderBatchAsync(IEnumerable orderIds) + { + var orders = orderIds.ToList(); + if (orders.Count == 0) + { + _logger.LogWarning("Order IDs cannot be null or empty"); + return []; + } + + try + { + // Join the order IDs with commas as specified in the API documentation + var orderIdsParam = string.Join(",", orders); + + var token = _configuration["Payment:Auth:Afdian"] ?? "_:_"; + var tokenParts = token.Split(':'); + var userId = tokenParts[0]; + token = tokenParts[1]; + var paramsJson = JsonSerializer.Serialize(new { out_trade_no = orderIdsParam }, JsonOptions); + var ts = (long)(DateTime.UtcNow - new DateTime(1970, 1, 1)) + .TotalSeconds; // Current timestamp in seconds + + var sign = CalculateSign(token, userId, paramsJson, ts); + + var client = _httpClientFactory.CreateClient(); + var request = new HttpRequestMessage(HttpMethod.Post, "https://afdian.com/api/open/query-order") + { + Content = new StringContent(JsonSerializer.Serialize(new + { + user_id = userId, + @params = paramsJson, + ts, + sign + }, JsonOptions), Encoding.UTF8, "application/json") + }; + + var response = await client.SendAsync(request); + if (!response.IsSuccessStatusCode) + { + _logger.LogError( + $"Response Error: {response.StatusCode}, {await response.Content.ReadAsStringAsync()}"); + return new List(); + } + + var result = await JsonSerializer.DeserializeAsync( + await response.Content.ReadAsStreamAsync(), JsonOptions); + + // Check if we have a valid response and orders in the list + if (result?.Data?.Orders != null && result.Data.Orders.Count != 0) return result.Data.Orders; + _logger.LogWarning($"No orders found with IDs: {orderIdsParam}"); + return []; + } + catch (Exception ex) + { + _logger.LogError(ex, $"Error fetching orders"); + throw; + } + } + + /// + /// Handle an incoming webhook from Afdian's payment platform + /// + /// The HTTP request containing webhook data + /// An action to process the received order + /// A WebhookResponse object to be returned to Afdian + public async Task HandleWebhook( + HttpRequest request, + Func? processOrderAction + ) + { + _logger.LogInformation("Received webhook request from afdian..."); + + try + { + // Read the request body + string requestBody; + using (var reader = new StreamReader(request.Body, Encoding.UTF8)) + { + requestBody = await reader.ReadToEndAsync(); + } + + if (string.IsNullOrEmpty(requestBody)) + { + _logger.LogError("Webhook request body is empty"); + return new WebhookResponse { ErrorCode = 400, ErrorMessage = "Empty request body" }; + } + + _logger.LogInformation($"Received webhook: {requestBody}"); + + // Parse the webhook data + var webhook = JsonSerializer.Deserialize(requestBody, JsonOptions); + + if (webhook == null) + { + _logger.LogError("Failed to parse webhook data"); + return new WebhookResponse { ErrorCode = 400, ErrorMessage = "Invalid webhook data" }; + } + + // Validate the webhook type + if (webhook.Data.Type != "order") + { + _logger.LogWarning($"Unsupported webhook type: {webhook.Data.Type}"); + return WebhookResponse.Success; + } + + // Process the order + try + { + // Check for duplicate order processing by storing processed order IDs + // (You would implement a more permanent storage mechanism for production) + if (processOrderAction != null) + await processOrderAction(webhook.Data); + else + _logger.LogInformation( + $"Order received but no processing action provided: {webhook.Data.Order.TradeNumber}"); + } + catch (Exception ex) + { + _logger.LogError(ex, $"Error processing order {webhook.Data.Order.TradeNumber}"); + // Still returning success to Afdian to prevent repeated callbacks + // Your system should handle the error internally + } + + // Return success response to Afdian + return WebhookResponse.Success; + } + catch (Exception ex) + { + _logger.LogError(ex, "Error handling webhook"); + return WebhookResponse.Success; + } + } + + public string? GetSubscriptionPlanId(string subscriptionKey) + { + var planId = _configuration[$"Payment:Subscriptions:Afdian:{subscriptionKey}"]; + + if (string.IsNullOrEmpty(planId)) + { + _logger.LogWarning($"Unknown subscription key: {subscriptionKey}"); + return null; + } + + return planId; + } +} + +public class OrderResponse +{ + [JsonPropertyName("ec")] public int ErrorCode { get; set; } + + [JsonPropertyName("em")] public string ErrorMessage { get; set; } = null!; + + [JsonPropertyName("data")] public OrderData Data { get; set; } = null!; +} + +public class OrderData +{ + [JsonPropertyName("list")] public List Orders { get; set; } = null!; + + [JsonPropertyName("total_count")] public int TotalCount { get; set; } + + [JsonPropertyName("total_page")] public int TotalPages { get; set; } + + [JsonPropertyName("request")] public RequestDetails Request { get; set; } = null!; +} + +public class OrderItem : ISubscriptionOrder +{ + [JsonPropertyName("out_trade_no")] public string TradeNumber { get; set; } = null!; + + [JsonPropertyName("user_id")] public string UserId { get; set; } = null!; + + [JsonPropertyName("plan_id")] public string PlanId { get; set; } = null!; + + [JsonPropertyName("month")] public int Months { get; set; } + + [JsonPropertyName("total_amount")] public string TotalAmount { get; set; } = null!; + + [JsonPropertyName("show_amount")] public string ShowAmount { get; set; } = null!; + + [JsonPropertyName("status")] public int Status { get; set; } + + [JsonPropertyName("remark")] public string Remark { get; set; } = null!; + + [JsonPropertyName("redeem_id")] public string RedeemId { get; set; } = null!; + + [JsonPropertyName("product_type")] public int ProductType { get; set; } + + [JsonPropertyName("discount")] public string Discount { get; set; } = null!; + + [JsonPropertyName("sku_detail")] public List SkuDetail { get; set; } = null!; + + [JsonPropertyName("create_time")] public long CreateTime { get; set; } + + [JsonPropertyName("user_name")] public string UserName { get; set; } = null!; + + [JsonPropertyName("plan_title")] public string PlanTitle { get; set; } = null!; + + [JsonPropertyName("user_private_id")] public string UserPrivateId { get; set; } = null!; + + [JsonPropertyName("address_person")] public string AddressPerson { get; set; } = null!; + + [JsonPropertyName("address_phone")] public string AddressPhone { get; set; } = null!; + + [JsonPropertyName("address_address")] public string AddressAddress { get; set; } = null!; + + public Instant BegunAt => Instant.FromUnixTimeSeconds(CreateTime); + + public Duration Duration => Duration.FromDays(Months * 30); + + public string Provider => "afdian"; + + public string Id => TradeNumber; + + public string SubscriptionId => PlanId; + + public string AccountId => UserId; +} + +public class RequestDetails +{ + [JsonPropertyName("user_id")] public string UserId { get; set; } = null!; + + [JsonPropertyName("params")] public string Params { get; set; } = null!; + + [JsonPropertyName("ts")] public long Timestamp { get; set; } + + [JsonPropertyName("sign")] public string Sign { get; set; } = null!; +} + +/// +/// Request structure for Afdian webhook +/// +public class WebhookRequest +{ + [JsonPropertyName("ec")] public int ErrorCode { get; set; } + + [JsonPropertyName("em")] public string ErrorMessage { get; set; } = null!; + + [JsonPropertyName("data")] public WebhookOrderData Data { get; set; } = null!; +} + +/// +/// Order data contained in the webhook +/// +public class WebhookOrderData +{ + [JsonPropertyName("type")] public string Type { get; set; } = null!; + + [JsonPropertyName("order")] public WebhookOrderDetails Order { get; set; } = null!; +} + +/// +/// Order details in the webhook +/// +public class WebhookOrderDetails : OrderItem +{ + [JsonPropertyName("custom_order_id")] public string CustomOrderId { get; set; } = null!; +} + +/// +/// Response structure to acknowledge webhook receipt +/// +public class WebhookResponse +{ + [JsonPropertyName("ec")] public int ErrorCode { get; set; } = 200; + + [JsonPropertyName("em")] public string ErrorMessage { get; set; } = ""; + + public static WebhookResponse Success => new() + { + ErrorCode = 200, + ErrorMessage = string.Empty + }; +} + +/// +/// SKU detail item +/// +public class SkuDetailItem +{ + [JsonPropertyName("sku_id")] public string SkuId { get; set; } = null!; + + [JsonPropertyName("count")] public int Count { get; set; } + + [JsonPropertyName("name")] public string Name { get; set; } = null!; + + [JsonPropertyName("album_id")] public string AlbumId { get; set; } = null!; + + [JsonPropertyName("pic")] public string Picture { get; set; } = null!; +} diff --git a/DysonNetwork.Pass/Wallet/PaymentHandlers/ISubscriptionOrder.cs b/DysonNetwork.Pass/Wallet/PaymentHandlers/ISubscriptionOrder.cs new file mode 100644 index 0000000..1b4cd58 --- /dev/null +++ b/DysonNetwork.Pass/Wallet/PaymentHandlers/ISubscriptionOrder.cs @@ -0,0 +1,18 @@ +using NodaTime; + +namespace DysonNetwork.Pass.Wallet.PaymentHandlers; + +public interface ISubscriptionOrder +{ + public string Id { get; } + + public string SubscriptionId { get; } + + public Instant BegunAt { get; } + + public Duration Duration { get; } + + public string Provider { get; } + + public string AccountId { get; } +} diff --git a/DysonNetwork.Pass/Wallet/PaymentService.cs b/DysonNetwork.Pass/Wallet/PaymentService.cs new file mode 100644 index 0000000..c01cd62 --- /dev/null +++ b/DysonNetwork.Pass/Wallet/PaymentService.cs @@ -0,0 +1,297 @@ +using System.Globalization; +using DysonNetwork.Pass.Account; +using DysonNetwork.Pass.Localization; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.Extensions.Localization; +using NodaTime; + +namespace DysonNetwork.Pass.Wallet; + +public class PaymentService( + AppDatabase db, + WalletService wat, + NotificationService nty, + IStringLocalizer localizer +) +{ + public async Task CreateOrderAsync( + Guid? payeeWalletId, + string currency, + decimal amount, + Duration? expiration = null, + string? appIdentifier = null, + Dictionary? meta = null, + bool reuseable = true + ) + { + // Check if there's an existing unpaid order that can be reused + if (reuseable && appIdentifier != null) + { + var existingOrder = await db.PaymentOrders + .Where(o => o.Status == OrderStatus.Unpaid && + o.PayeeWalletId == payeeWalletId && + o.Currency == currency && + o.Amount == amount && + o.AppIdentifier == appIdentifier && + o.ExpiredAt > SystemClock.Instance.GetCurrentInstant()) + .FirstOrDefaultAsync(); + + // If an existing order is found, check if meta matches + if (existingOrder != null && meta != null && existingOrder.Meta != null) + { + // Compare meta dictionaries - if they are equivalent, reuse the order + var metaMatches = existingOrder.Meta.Count == meta.Count && + !existingOrder.Meta.Except(meta).Any(); + + if (metaMatches) + { + return existingOrder; + } + } + } + + // Create a new order if no reusable order was found + var order = new Order + { + PayeeWalletId = payeeWalletId, + Currency = currency, + Amount = amount, + ExpiredAt = SystemClock.Instance.GetCurrentInstant().Plus(expiration ?? Duration.FromHours(24)), + AppIdentifier = appIdentifier, + Meta = meta + }; + + db.PaymentOrders.Add(order); + await db.SaveChangesAsync(); + return order; + } + + public async Task CreateTransactionWithAccountAsync( + Guid? payerAccountId, + Guid? payeeAccountId, + string currency, + decimal amount, + string? remarks = null, + TransactionType type = TransactionType.System + ) + { + Wallet? payer = null, payee = null; + if (payerAccountId.HasValue) + payer = await db.Wallets.FirstOrDefaultAsync(e => e.AccountId == payerAccountId.Value); + if (payeeAccountId.HasValue) + payee = await db.Wallets.FirstOrDefaultAsync(e => e.AccountId == payeeAccountId.Value); + + if (payer == null && payerAccountId.HasValue) + throw new ArgumentException("Payer account was specified, but wallet was not found"); + if (payee == null && payeeAccountId.HasValue) + throw new ArgumentException("Payee account was specified, but wallet was not found"); + + return await CreateTransactionAsync( + payer?.Id, + payee?.Id, + currency, + amount, + remarks, + type + ); + } + + public async Task CreateTransactionAsync( + Guid? payerWalletId, + Guid? payeeWalletId, + string currency, + decimal amount, + string? remarks = null, + TransactionType type = TransactionType.System + ) + { + if (payerWalletId == null && payeeWalletId == null) + throw new ArgumentException("At least one wallet must be specified."); + if (amount <= 0) throw new ArgumentException("Cannot create transaction with negative or zero amount."); + + var transaction = new Transaction + { + PayerWalletId = payerWalletId, + PayeeWalletId = payeeWalletId, + Currency = currency, + Amount = amount, + Remarks = remarks, + Type = type + }; + + if (payerWalletId.HasValue) + { + var (payerPocket, isNewlyCreated) = + await wat.GetOrCreateWalletPocketAsync(payerWalletId.Value, currency); + + if (isNewlyCreated || payerPocket.Amount < amount) + throw new InvalidOperationException("Insufficient funds"); + + await db.WalletPockets + .Where(p => p.Id == payerPocket.Id && p.Amount >= amount) + .ExecuteUpdateAsync(s => + s.SetProperty(p => p.Amount, p => p.Amount - amount)); + } + + if (payeeWalletId.HasValue) + { + var (payeePocket, isNewlyCreated) = + await wat.GetOrCreateWalletPocketAsync(payeeWalletId.Value, currency, amount); + + if (!isNewlyCreated) + await db.WalletPockets + .Where(p => p.Id == payeePocket.Id) + .ExecuteUpdateAsync(s => + s.SetProperty(p => p.Amount, p => p.Amount + amount)); + } + + db.PaymentTransactions.Add(transaction); + await db.SaveChangesAsync(); + return transaction; + } + + public async Task PayOrderAsync(Guid orderId, Guid payerWalletId) + { + var order = await db.PaymentOrders + .Include(o => o.Transaction) + .FirstOrDefaultAsync(o => o.Id == orderId); + + if (order == null) + { + throw new InvalidOperationException("Order not found"); + } + + if (order.Status != OrderStatus.Unpaid) + { + throw new InvalidOperationException($"Order is in invalid status: {order.Status}"); + } + + if (order.ExpiredAt < SystemClock.Instance.GetCurrentInstant()) + { + order.Status = OrderStatus.Expired; + await db.SaveChangesAsync(); + throw new InvalidOperationException("Order has expired"); + } + + var transaction = await CreateTransactionAsync( + payerWalletId, + order.PayeeWalletId, + order.Currency, + order.Amount, + order.Remarks ?? $"Payment for Order #{order.Id}", + type: TransactionType.Order); + + order.TransactionId = transaction.Id; + order.Transaction = transaction; + order.Status = OrderStatus.Paid; + + await db.SaveChangesAsync(); + + await NotifyOrderPaid(order); + + return order; + } + + private async Task NotifyOrderPaid(Order order) + { + if (order.PayeeWallet is null) return; + var account = await db.Accounts.FirstOrDefaultAsync(a => a.Id == order.PayeeWallet.AccountId); + if (account is null) return; + + AccountService.SetCultureInfo(account); + + // Due to ID is uuid, it longer than 8 words for sure + var readableOrderId = order.Id.ToString().Replace("-", "")[..8]; + var readableOrderRemark = order.Remarks ?? $"#{readableOrderId}"; + + await nty.SendNotification( + account, + "wallets.orders.paid", + localizer["OrderPaidTitle", $"#{readableOrderId}"], + null, + localizer["OrderPaidBody", order.Amount.ToString(CultureInfo.InvariantCulture), order.Currency, + readableOrderRemark], + new Dictionary() + { + ["order_id"] = order.Id.ToString() + } + ); + } + + public async Task CancelOrderAsync(Guid orderId) + { + var order = await db.PaymentOrders.FindAsync(orderId); + if (order == null) + { + throw new InvalidOperationException("Order not found"); + } + + if (order.Status != OrderStatus.Unpaid) + { + throw new InvalidOperationException($"Cannot cancel order in status: {order.Status}"); + } + + order.Status = OrderStatus.Cancelled; + await db.SaveChangesAsync(); + return order; + } + + public async Task<(Order Order, Transaction RefundTransaction)> RefundOrderAsync(Guid orderId) + { + var order = await db.PaymentOrders + .Include(o => o.Transaction) + .FirstOrDefaultAsync(o => o.Id == orderId); + + if (order == null) + { + throw new InvalidOperationException("Order not found"); + } + + if (order.Status != OrderStatus.Paid) + { + throw new InvalidOperationException($"Cannot refund order in status: {order.Status}"); + } + + if (order.Transaction == null) + { + throw new InvalidOperationException("Order has no associated transaction"); + } + + var refundTransaction = await CreateTransactionAsync( + order.PayeeWalletId, + order.Transaction.PayerWalletId, + order.Currency, + order.Amount, + $"Refund for order {order.Id}"); + + order.Status = OrderStatus.Finished; + await db.SaveChangesAsync(); + + return (order, refundTransaction); + } + + public async Task TransferAsync(Guid payerAccountId, Guid payeeAccountId, string currency, + decimal amount) + { + var payerWallet = await wat.GetWalletAsync(payerAccountId); + if (payerWallet == null) + { + throw new InvalidOperationException($"Payer wallet not found for account {payerAccountId}"); + } + + var payeeWallet = await wat.GetWalletAsync(payeeAccountId); + if (payeeWallet == null) + { + throw new InvalidOperationException($"Payee wallet not found for account {payeeAccountId}"); + } + + return await CreateTransactionAsync( + payerWallet.Id, + payeeWallet.Id, + currency, + amount, + $"Transfer from account {payerAccountId} to {payeeAccountId}", + TransactionType.Transfer); + } +} \ No newline at end of file diff --git a/DysonNetwork.Pass/Wallet/Subscription.cs b/DysonNetwork.Pass/Wallet/Subscription.cs new file mode 100644 index 0000000..dd37c5c --- /dev/null +++ b/DysonNetwork.Pass/Wallet/Subscription.cs @@ -0,0 +1,233 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using DysonNetwork.Shared.Data; +using Microsoft.EntityFrameworkCore; +using NodaTime; + +namespace DysonNetwork.Pass.Wallet; + +public record class SubscriptionTypeData( + string Identifier, + string? GroupIdentifier, + string Currency, + decimal BasePrice, + int? RequiredLevel = null +) +{ + public static readonly Dictionary SubscriptionDict = + new() + { + [SubscriptionType.Twinkle] = new SubscriptionTypeData( + SubscriptionType.Twinkle, + SubscriptionType.StellarProgram, + WalletCurrency.SourcePoint, + 0, + 1 + ), + [SubscriptionType.Stellar] = new SubscriptionTypeData( + SubscriptionType.Stellar, + SubscriptionType.StellarProgram, + WalletCurrency.SourcePoint, + 1200, + 3 + ), + [SubscriptionType.Nova] = new SubscriptionTypeData( + SubscriptionType.Nova, + SubscriptionType.StellarProgram, + WalletCurrency.SourcePoint, + 2400, + 6 + ), + [SubscriptionType.Supernova] = new SubscriptionTypeData( + SubscriptionType.Supernova, + SubscriptionType.StellarProgram, + WalletCurrency.SourcePoint, + 3600, + 9 + ) + }; + + public static readonly Dictionary SubscriptionHumanReadable = + new() + { + [SubscriptionType.Twinkle] = "Stellar Program Twinkle", + [SubscriptionType.Stellar] = "Stellar Program", + [SubscriptionType.Nova] = "Stellar Program Nova", + [SubscriptionType.Supernova] = "Stellar Program Supernova" + }; +} + +public abstract class SubscriptionType +{ + /// + /// DO NOT USE THIS TYPE DIRECTLY, + /// this is the prefix of all the stellar program subscriptions. + /// + public const string StellarProgram = "solian.stellar"; + + /// + /// No actual usage, just tells there is a free level named twinkle. + /// Applies to every registered user by default, so there is no need to create a record in db for that. + /// + public const string Twinkle = "solian.stellar.twinkle"; + + public const string Stellar = "solian.stellar.primary"; + public const string Nova = "solian.stellar.nova"; + public const string Supernova = "solian.stellar.supernova"; +} + +public abstract class SubscriptionPaymentMethod +{ + /// + /// The solar points / solar dollars. + /// + public const string InAppWallet = "solian.wallet"; + + /// + /// afdian.com + /// aka. China patreon + /// + public const string Afdian = "afdian"; +} + +public enum SubscriptionStatus +{ + Unpaid, + Active, + Expired, + Cancelled +} + +/// +/// The subscription is for the Stellar Program in most cases. +/// The paid subscription in another word. +/// +[Index(nameof(Identifier))] +public class Subscription : ModelBase +{ + public Guid Id { get; set; } = Guid.NewGuid(); + public Instant BegunAt { get; set; } + public Instant? EndedAt { get; set; } + + /// + /// The type of the subscriptions + /// + [MaxLength(4096)] + public string Identifier { get; set; } = null!; + + /// + /// The field is used to override the activation status of the membership. + /// Might be used for refund handling and other special cases. + /// + /// Go see the IsAvailable field if you want to get real the status of the membership. + /// + public bool IsActive { get; set; } = true; + + /// + /// Indicates is the current user got the membership for free, + /// to prevent giving the same discount for the same user again. + /// + public bool IsFreeTrial { get; set; } + + public SubscriptionStatus Status { get; set; } = SubscriptionStatus.Unpaid; + + [MaxLength(4096)] public string PaymentMethod { get; set; } = null!; + [Column(TypeName = "jsonb")] public PaymentDetails PaymentDetails { get; set; } = null!; + public decimal BasePrice { get; set; } + public Guid? CouponId { get; set; } + public Coupon? Coupon { get; set; } + public Instant? RenewalAt { get; set; } + + public Guid AccountId { get; set; } + public Account.Account Account { get; set; } = null!; + + [NotMapped] + public bool IsAvailable + { + get + { + if (!IsActive) return false; + + var now = SystemClock.Instance.GetCurrentInstant(); + + if (BegunAt > now) return false; + if (EndedAt.HasValue && now > EndedAt.Value) return false; + if (RenewalAt.HasValue && now > RenewalAt.Value) return false; + if (Status != SubscriptionStatus.Active) return false; + + return true; + } + } + + [NotMapped] + public decimal FinalPrice + { + get + { + if (IsFreeTrial) return 0; + if (Coupon == null) return BasePrice; + + var now = SystemClock.Instance.GetCurrentInstant(); + if (Coupon.AffectedAt.HasValue && now < Coupon.AffectedAt.Value || + Coupon.ExpiredAt.HasValue && now > Coupon.ExpiredAt.Value) return BasePrice; + + if (Coupon.DiscountAmount.HasValue) return BasePrice - Coupon.DiscountAmount.Value; + if (Coupon.DiscountRate.HasValue) return BasePrice * (decimal)(1 - Coupon.DiscountRate.Value); + return BasePrice; + } + } +} + +public class PaymentDetails +{ + public string Currency { get; set; } = null!; + public string? OrderId { get; set; } +} + +/// +/// A discount that can applies in purchases among the Solar Network. +/// For now, it can be used in the subscription purchase. +/// +public class Coupon : ModelBase +{ + public Guid Id { get; set; } = Guid.NewGuid(); + + /// + /// The items that can apply this coupon. + /// Leave it to null to apply to all items. + /// + [MaxLength(4096)] + public string? Identifier { get; set; } + + /// + /// The code that human-readable and memorizable. + /// Leave it blank to use it only with the ID. + /// + [MaxLength(1024)] + public string? Code { get; set; } + + public Instant? AffectedAt { get; set; } + public Instant? ExpiredAt { get; set; } + + /// + /// The amount of the discount. + /// If this field and the rate field are both not null, + /// the amount discount will be applied and the discount rate will be ignored. + /// Formula: final price = base price - discount amount + /// + public decimal? DiscountAmount { get; set; } + + /// + /// The percentage of the discount. + /// If this field and the amount field are both not null, + /// this field will be ignored. + /// Formula: final price = base price * (1 - discount rate) + /// + public double? DiscountRate { get; set; } + + /// + /// The max usage of the current coupon. + /// Leave it to null to use it unlimited. + /// + public int? MaxUsage { get; set; } +} \ No newline at end of file diff --git a/DysonNetwork.Pass/Wallet/SubscriptionController.cs b/DysonNetwork.Pass/Wallet/SubscriptionController.cs new file mode 100644 index 0000000..1e4efc4 --- /dev/null +++ b/DysonNetwork.Pass/Wallet/SubscriptionController.cs @@ -0,0 +1,204 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using NodaTime; +using System.ComponentModel.DataAnnotations; +using DysonNetwork.Pass.Wallet.PaymentHandlers; + +namespace DysonNetwork.Pass.Wallet; + +[ApiController] +[Route("/api/subscriptions")] +public class SubscriptionController(SubscriptionService subscriptions, AfdianPaymentHandler afdian, AppDatabase db) : ControllerBase +{ + [HttpGet] + [Authorize] + public async Task>> ListSubscriptions( + [FromQuery] int offset = 0, + [FromQuery] int take = 20 + ) + { + if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); + + var query = db.WalletSubscriptions.AsQueryable() + .Where(s => s.AccountId == currentUser.Id) + .Include(s => s.Coupon) + .OrderByDescending(s => s.BegunAt); + + var totalCount = await query.CountAsync(); + + var subscriptionsList = await query + .Skip(offset) + .Take(take) + .ToListAsync(); + + Response.Headers["X-Total"] = totalCount.ToString(); + + return subscriptionsList; + } + + [HttpGet("fuzzy/{prefix}")] + [Authorize] + public async Task> GetSubscriptionFuzzy(string prefix) + { + if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); + + var subs = await db.WalletSubscriptions + .Where(s => s.AccountId == currentUser.Id && s.IsActive) + .Where(s => EF.Functions.ILike(s.Identifier, prefix + "%")) + .OrderByDescending(s => s.BegunAt) + .ToListAsync(); + if (subs.Count == 0) return NotFound(); + var subscription = subs.FirstOrDefault(s => s.IsAvailable); + if (subscription is null) return NotFound(); + + return Ok(subscription); + } + + [HttpGet("{identifier}")] + [Authorize] + public async Task> GetSubscription(string identifier) + { + if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); + + var subscription = await subscriptions.GetSubscriptionAsync(currentUser.Id, identifier); + if (subscription is null) return NotFound($"Subscription with identifier {identifier} was not found."); + + return subscription; + } + + public class CreateSubscriptionRequest + { + [Required] public string Identifier { get; set; } = null!; + [Required] public string PaymentMethod { get; set; } = null!; + [Required] public PaymentDetails PaymentDetails { get; set; } = null!; + public string? Coupon { get; set; } + public int? CycleDurationDays { get; set; } + public bool IsFreeTrial { get; set; } = false; + public bool IsAutoRenewal { get; set; } = true; + } + + [HttpPost] + [Authorize] + public async Task> CreateSubscription( + [FromBody] CreateSubscriptionRequest request, + [FromHeader(Name = "X-Noop")] bool noop = false + ) + { + if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); + + Duration? cycleDuration = null; + if (request.CycleDurationDays.HasValue) + cycleDuration = Duration.FromDays(request.CycleDurationDays.Value); + + try + { + var subscription = await subscriptions.CreateSubscriptionAsync( + currentUser, + request.Identifier, + request.PaymentMethod, + request.PaymentDetails, + cycleDuration, + request.Coupon, + request.IsFreeTrial, + request.IsAutoRenewal, + noop + ); + + return subscription; + } + catch (ArgumentOutOfRangeException ex) + { + return BadRequest(ex.Message); + } + catch (InvalidOperationException ex) + { + return BadRequest(ex.Message); + } + } + + [HttpPost("{identifier}/cancel")] + [Authorize] + public async Task> CancelSubscription(string identifier) + { + if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); + + try + { + var subscription = await subscriptions.CancelSubscriptionAsync(currentUser.Id, identifier); + return subscription; + } + catch (InvalidOperationException ex) + { + return BadRequest(ex.Message); + } + } + + [HttpPost("{identifier}/order")] + [Authorize] + public async Task> CreateSubscriptionOrder(string identifier) + { + if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); + + try + { + var order = await subscriptions.CreateSubscriptionOrder(currentUser.Id, identifier); + return order; + } + catch (InvalidOperationException ex) + { + return BadRequest(ex.Message); + } + } + + public class SubscriptionOrderRequest + { + [Required] public Guid OrderId { get; set; } + } + + [HttpPost("order/handle")] + [Authorize] + public async Task> HandleSubscriptionOrder([FromBody] SubscriptionOrderRequest request) + { + var order = await db.PaymentOrders.FindAsync(request.OrderId); + if (order is null) return NotFound($"Order with ID {request.OrderId} was not found."); + + try + { + var subscription = await subscriptions.HandleSubscriptionOrder(order); + return subscription; + } + catch (InvalidOperationException ex) + { + return BadRequest(ex.Message); + } + } + + public class RestorePurchaseRequest + { + [Required] public string OrderId { get; set; } = null!; + } + + [HttpPost("order/restore/afdian")] + [Authorize] + public async Task RestorePurchaseFromAfdian([FromBody] RestorePurchaseRequest request) + { + var order = await afdian.GetOrderAsync(request.OrderId); + if (order is null) return NotFound($"Order with ID {request.OrderId} was not found."); + + var subscription = await subscriptions.CreateSubscriptionFromOrder(order); + return Ok(subscription); + } + + [HttpPost("order/handle/afdian")] + public async Task> AfdianWebhook() + { + var response = await afdian.HandleWebhook(Request, async webhookData => + { + var order = webhookData.Order; + await subscriptions.CreateSubscriptionFromOrder(order); + }); + + return Ok(response); + } +} diff --git a/DysonNetwork.Pass/Wallet/SubscriptionRenewalJob.cs b/DysonNetwork.Pass/Wallet/SubscriptionRenewalJob.cs new file mode 100644 index 0000000..5ca89e7 --- /dev/null +++ b/DysonNetwork.Pass/Wallet/SubscriptionRenewalJob.cs @@ -0,0 +1,137 @@ +using Microsoft.EntityFrameworkCore; +using NodaTime; +using Quartz; + +namespace DysonNetwork.Pass.Wallet; + +public class SubscriptionRenewalJob( + AppDatabase db, + SubscriptionService subscriptionService, + PaymentService paymentService, + WalletService walletService, + ILogger logger +) : IJob +{ + public async Task Execute(IJobExecutionContext context) + { + logger.LogInformation("Starting subscription auto-renewal job..."); + + // First update expired subscriptions + var expiredCount = await subscriptionService.UpdateExpiredSubscriptionsAsync(); + logger.LogInformation("Updated {ExpiredCount} expired subscriptions", expiredCount); + + var now = SystemClock.Instance.GetCurrentInstant(); + const int batchSize = 100; // Process in smaller batches + var processedCount = 0; + var renewedCount = 0; + var failedCount = 0; + + // Find subscriptions that need renewal (due for renewal and are still active) + var subscriptionsToRenew = await db.WalletSubscriptions + .Where(s => s.RenewalAt.HasValue && s.RenewalAt.Value <= now) // Due for renewal + .Where(s => s.Status == SubscriptionStatus.Active) // Only paid subscriptions + .Where(s => s.IsActive) // Only active subscriptions + .Where(s => !s.IsFreeTrial) // Exclude free trials + .OrderBy(s => s.RenewalAt) // Process oldest first + .Take(batchSize) + .Include(s => s.Coupon) // Include coupon information + .ToListAsync(); + + var totalSubscriptions = subscriptionsToRenew.Count; + logger.LogInformation("Found {TotalSubscriptions} subscriptions due for renewal", totalSubscriptions); + + foreach (var subscription in subscriptionsToRenew) + { + try + { + processedCount++; + logger.LogDebug( + "Processing renewal for subscription {SubscriptionId} (Identifier: {Identifier}) for account {AccountId}", + subscription.Id, subscription.Identifier, subscription.AccountId); + + if (subscription.RenewalAt is null) + { + logger.LogWarning( + "Subscription {SubscriptionId} (Identifier: {Identifier}) has no renewal date or has been cancelled.", + subscription.Id, subscription.Identifier); + subscription.Status = SubscriptionStatus.Cancelled; + db.WalletSubscriptions.Update(subscription); + await db.SaveChangesAsync(); + continue; + } + + // Calculate next cycle duration based on current cycle + var currentCycle = subscription.EndedAt!.Value - subscription.BegunAt; + + // Create an order for the renewal payment + var order = await paymentService.CreateOrderAsync( + null, + WalletCurrency.GoldenPoint, + subscription.FinalPrice, + appIdentifier: SubscriptionService.SubscriptionOrderIdentifier, + meta: new Dictionary() + { + ["subscription_id"] = subscription.Id.ToString(), + ["subscription_identifier"] = subscription.Identifier, + ["is_renewal"] = true + } + ); + + // Try to process the payment automatically + if (subscription.PaymentMethod == SubscriptionPaymentMethod.InAppWallet) + { + try + { + var wallet = await walletService.GetWalletAsync(subscription.AccountId); + if (wallet is null) continue; + + // Process automatic payment from wallet + await paymentService.PayOrderAsync(order.Id, wallet.Id); + + // Update subscription details + subscription.BegunAt = subscription.EndedAt!.Value; + subscription.EndedAt = subscription.BegunAt.Plus(currentCycle); + subscription.RenewalAt = subscription.EndedAt; + + db.WalletSubscriptions.Update(subscription); + await db.SaveChangesAsync(); + + renewedCount++; + logger.LogInformation("Successfully renewed subscription {SubscriptionId}", subscription.Id); + } + catch (Exception ex) + { + // If auto-payment fails, mark for manual payment + logger.LogWarning(ex, "Failed to auto-renew subscription {SubscriptionId} with wallet payment", + subscription.Id); + failedCount++; + } + } + else + { + // For other payment methods, mark as pending payment + logger.LogInformation("Subscription {SubscriptionId} requires manual payment via {PaymentMethod}", + subscription.Id, subscription.PaymentMethod); + failedCount++; + } + } + catch (Exception ex) + { + logger.LogError(ex, "Error processing subscription {SubscriptionId}", subscription.Id); + failedCount++; + } + + // Log progress periodically + if (processedCount % 20 == 0 || processedCount == totalSubscriptions) + { + logger.LogInformation( + "Progress: processed {ProcessedCount}/{TotalSubscriptions} subscriptions, {RenewedCount} renewed, {FailedCount} failed", + processedCount, totalSubscriptions, renewedCount, failedCount); + } + } + + logger.LogInformation( + "Completed subscription renewal job. Processed: {ProcessedCount}, Renewed: {RenewedCount}, Failed: {FailedCount}", + processedCount, renewedCount, failedCount); + } +} \ No newline at end of file diff --git a/DysonNetwork.Pass/Wallet/SubscriptionService.cs b/DysonNetwork.Pass/Wallet/SubscriptionService.cs new file mode 100644 index 0000000..b999d0f --- /dev/null +++ b/DysonNetwork.Pass/Wallet/SubscriptionService.cs @@ -0,0 +1,394 @@ +using System.Text.Json; +using DysonNetwork.Pass.Account; +using DysonNetwork.Pass.Localization; +using DysonNetwork.Pass.Wallet.PaymentHandlers; +using DysonNetwork.Shared.Cache; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Localization; +using NodaTime; + +namespace DysonNetwork.Pass.Wallet; + +public class SubscriptionService( + AppDatabase db, + PaymentService payment, + AccountService accounts, + NotificationService nty, + IStringLocalizer localizer, + IConfiguration configuration, + ICacheService cache, + ILogger logger +) +{ + public async Task CreateSubscriptionAsync( + Account.Account account, + string identifier, + string paymentMethod, + PaymentDetails paymentDetails, + Duration? cycleDuration = null, + string? coupon = null, + bool isFreeTrial = false, + bool isAutoRenewal = true, + bool noop = false + ) + { + var subscriptionInfo = SubscriptionTypeData + .SubscriptionDict.TryGetValue(identifier, out var template) + ? template + : null; + if (subscriptionInfo is null) + throw new ArgumentOutOfRangeException(nameof(identifier), $@"Subscription {identifier} was not found."); + var subscriptionsInGroup = subscriptionInfo.GroupIdentifier is not null + ? SubscriptionTypeData.SubscriptionDict + .Where(s => s.Value.GroupIdentifier == subscriptionInfo.GroupIdentifier) + .Select(s => s.Value.Identifier) + .ToArray() + : [identifier]; + + cycleDuration ??= Duration.FromDays(30); + + var existingSubscription = await GetSubscriptionAsync(account.Id, subscriptionsInGroup); + if (existingSubscription is not null && !noop) + throw new InvalidOperationException($"Active subscription with identifier {identifier} already exists."); + if (existingSubscription is not null) + return existingSubscription; + + if (subscriptionInfo.RequiredLevel > 0) + { + var profile = await db.AccountProfiles + .Where(p => p.AccountId == account.Id) + .FirstOrDefaultAsync(); + if (profile is null) throw new InvalidOperationException("Account profile was not found."); + if (profile.Level < subscriptionInfo.RequiredLevel) + throw new InvalidOperationException( + $"Account level must be at least {subscriptionInfo.RequiredLevel} to subscribe to {identifier}." + ); + } + + if (isFreeTrial) + { + var prevFreeTrial = await db.WalletSubscriptions + .Where(s => s.AccountId == account.Id && s.Identifier == identifier && s.IsFreeTrial) + .FirstOrDefaultAsync(); + if (prevFreeTrial is not null) + throw new InvalidOperationException("Free trial already exists."); + } + + Coupon? couponData = null; + if (coupon is not null) + { + var inputCouponId = Guid.TryParse(coupon, out var parsedCouponId) ? parsedCouponId : Guid.Empty; + couponData = await db.WalletCoupons + .Where(c => (c.Id == inputCouponId) || (c.Identifier != null && c.Identifier == coupon)) + .FirstOrDefaultAsync(); + if (couponData is null) throw new InvalidOperationException($"Coupon {coupon} was not found."); + } + + var now = SystemClock.Instance.GetCurrentInstant(); + var subscription = new Subscription + { + BegunAt = now, + EndedAt = now.Plus(cycleDuration.Value), + Identifier = identifier, + IsActive = true, + IsFreeTrial = isFreeTrial, + Status = SubscriptionStatus.Unpaid, + PaymentMethod = paymentMethod, + PaymentDetails = paymentDetails, + BasePrice = subscriptionInfo.BasePrice, + CouponId = couponData?.Id, + Coupon = couponData, + RenewalAt = (isFreeTrial || !isAutoRenewal) ? null : now.Plus(cycleDuration.Value), + AccountId = account.Id, + }; + + db.WalletSubscriptions.Add(subscription); + await db.SaveChangesAsync(); + + return subscription; + } + + public async Task CreateSubscriptionFromOrder(ISubscriptionOrder order) + { + var cfgSection = configuration.GetSection("Payment:Subscriptions"); + var provider = order.Provider; + + var currency = "irl"; + var subscriptionIdentifier = order.SubscriptionId; + switch (provider) + { + case "afdian": + // Get the Afdian section first, then bind it to a dictionary + var afdianPlans = cfgSection.GetSection("Afdian").Get>(); + logger.LogInformation("Afdian plans configuration: {Plans}", JsonSerializer.Serialize(afdianPlans)); + if (afdianPlans != null && afdianPlans.TryGetValue(subscriptionIdentifier, out var planName)) + subscriptionIdentifier = planName; + currency = "cny"; + break; + } + + var subscriptionTemplate = SubscriptionTypeData + .SubscriptionDict.TryGetValue(subscriptionIdentifier, out var template) + ? template + : null; + if (subscriptionTemplate is null) + throw new ArgumentOutOfRangeException(nameof(subscriptionIdentifier), + $@"Subscription {subscriptionIdentifier} was not found."); + + Account.Account? account = null; + if (!string.IsNullOrEmpty(provider)) + account = await accounts.LookupAccountByConnection(order.AccountId, provider); + else if (Guid.TryParse(order.AccountId, out var accountId)) + account = await db.Accounts.FirstOrDefaultAsync(a => a.Id == accountId); + + if (account is null) + throw new InvalidOperationException($"Account was not found with identifier {order.AccountId}"); + + var cycleDuration = order.Duration; + + var existingSubscription = await GetSubscriptionAsync(account.Id, subscriptionIdentifier); + if (existingSubscription is not null && existingSubscription.PaymentMethod != provider) + throw new InvalidOperationException( + $"Active subscription with identifier {subscriptionIdentifier} already exists."); + if (existingSubscription?.PaymentDetails.OrderId == order.Id) + return existingSubscription; + if (existingSubscription is not null) + { + // Same provider, but different order, renew the subscription + existingSubscription.PaymentDetails.OrderId = order.Id; + existingSubscription.EndedAt = order.BegunAt.Plus(cycleDuration); + existingSubscription.RenewalAt = order.BegunAt.Plus(cycleDuration); + existingSubscription.Status = SubscriptionStatus.Active; + + db.Update(existingSubscription); + await db.SaveChangesAsync(); + + return existingSubscription; + } + + var subscription = new Subscription + { + BegunAt = order.BegunAt, + EndedAt = order.BegunAt.Plus(cycleDuration), + IsActive = true, + Status = SubscriptionStatus.Active, + Identifier = subscriptionIdentifier, + PaymentMethod = provider, + PaymentDetails = new PaymentDetails + { + Currency = currency, + OrderId = order.Id, + }, + BasePrice = subscriptionTemplate.BasePrice, + RenewalAt = order.BegunAt.Plus(cycleDuration), + AccountId = account.Id, + }; + + db.WalletSubscriptions.Add(subscription); + await db.SaveChangesAsync(); + + await NotifySubscriptionBegun(subscription); + + return subscription; + } + + /// + /// Cancel the renewal of the current activated subscription. + /// + /// The user who requested the action. + /// The subscription identifier + /// + /// The active subscription was not found + public async Task CancelSubscriptionAsync(Guid accountId, string identifier) + { + var subscription = await GetSubscriptionAsync(accountId, identifier); + if (subscription is null) + throw new InvalidOperationException($"Subscription with identifier {identifier} was not found."); + if (subscription.Status != SubscriptionStatus.Active) + throw new InvalidOperationException("Subscription is already cancelled."); + if (subscription.RenewalAt is null) + throw new InvalidOperationException("Subscription is no need to be cancelled."); + if (subscription.PaymentMethod != SubscriptionPaymentMethod.InAppWallet) + throw new InvalidOperationException( + "Only in-app wallet subscription can be cancelled. For other payment methods, please head to the payment provider." + ); + + subscription.RenewalAt = null; + + await db.SaveChangesAsync(); + + // Invalidate the cache for this subscription + var cacheKey = $"{SubscriptionCacheKeyPrefix}{accountId}:{identifier}"; + await cache.RemoveAsync(cacheKey); + + return subscription; + } + + public const string SubscriptionOrderIdentifier = "solian.subscription.order"; + + /// + /// Creates a subscription order for an unpaid or expired subscription. + /// If the subscription is active, it will extend its expiration date. + /// + /// The unique identifier for the account associated with the subscription. + /// The unique subscription identifier. + /// A task that represents the asynchronous operation. The task result contains the created subscription order. + /// Thrown when no matching unpaid or expired subscription is found. + public async Task CreateSubscriptionOrder(Guid accountId, string identifier) + { + var subscription = await db.WalletSubscriptions + .Where(s => s.AccountId == accountId && s.Identifier == identifier) + .Where(s => s.Status != SubscriptionStatus.Expired) + .Include(s => s.Coupon) + .OrderByDescending(s => s.BegunAt) + .FirstOrDefaultAsync(); + if (subscription is null) throw new InvalidOperationException("No matching subscription found."); + + var subscriptionInfo = SubscriptionTypeData.SubscriptionDict + .TryGetValue(subscription.Identifier, out var template) + ? template + : null; + if (subscriptionInfo is null) throw new InvalidOperationException("No matching subscription found."); + + return await payment.CreateOrderAsync( + null, + subscriptionInfo.Currency, + subscription.FinalPrice, + appIdentifier: SubscriptionOrderIdentifier, + meta: new Dictionary() + { + ["subscription_id"] = subscription.Id.ToString(), + ["subscription_identifier"] = subscription.Identifier, + } + ); + } + + public async Task HandleSubscriptionOrder(Order order) + { + if (order.AppIdentifier != SubscriptionOrderIdentifier || order.Status != OrderStatus.Paid || + order.Meta?["subscription_id"] is not JsonElement subscriptionIdJson) + throw new InvalidOperationException("Invalid order."); + + var subscriptionId = Guid.TryParse(subscriptionIdJson.ToString(), out var parsedSubscriptionId) + ? parsedSubscriptionId + : Guid.Empty; + if (subscriptionId == Guid.Empty) + throw new InvalidOperationException("Invalid order."); + var subscription = await db.WalletSubscriptions + .Where(s => s.Id == subscriptionId) + .Include(s => s.Coupon) + .FirstOrDefaultAsync(); + if (subscription is null) + throw new InvalidOperationException("Invalid order."); + + if (subscription.Status == SubscriptionStatus.Expired) + { + var now = SystemClock.Instance.GetCurrentInstant(); + var cycle = subscription.BegunAt.Minus(subscription.RenewalAt ?? subscription.EndedAt ?? now); + + var nextRenewalAt = subscription.RenewalAt?.Plus(cycle); + var nextEndedAt = subscription.EndedAt?.Plus(cycle); + + subscription.RenewalAt = nextRenewalAt; + subscription.EndedAt = nextEndedAt; + } + + subscription.Status = SubscriptionStatus.Active; + + db.Update(subscription); + await db.SaveChangesAsync(); + + await NotifySubscriptionBegun(subscription); + + return subscription; + } + + /// + /// Updates the status of expired subscriptions to reflect their current state. + /// This helps maintain accurate subscription records and is typically called periodically. + /// + /// Maximum number of subscriptions to process + /// Number of subscriptions that were marked as expired + public async Task UpdateExpiredSubscriptionsAsync(int batchSize = 100) + { + var now = SystemClock.Instance.GetCurrentInstant(); + + // Find active subscriptions that have passed their end date + var expiredSubscriptions = await db.WalletSubscriptions + .Where(s => s.IsActive) + .Where(s => s.Status == SubscriptionStatus.Active) + .Where(s => s.EndedAt.HasValue && s.EndedAt.Value < now) + .Take(batchSize) + .ToListAsync(); + + if (expiredSubscriptions.Count == 0) + return 0; + + foreach (var subscription in expiredSubscriptions) + { + subscription.Status = SubscriptionStatus.Expired; + + // Clear the cache for this subscription + var cacheKey = $"{SubscriptionCacheKeyPrefix}{subscription.AccountId}:{subscription.Identifier}"; + await cache.RemoveAsync(cacheKey); + } + + await db.SaveChangesAsync(); + return expiredSubscriptions.Count; + } + + private async Task NotifySubscriptionBegun(Subscription subscription) + { + var account = await db.Accounts.FirstOrDefaultAsync(a => a.Id == subscription.AccountId); + if (account is null) return; + + AccountService.SetCultureInfo(account); + + var humanReadableName = + SubscriptionTypeData.SubscriptionHumanReadable.TryGetValue(subscription.Identifier, out var humanReadable) + ? humanReadable + : subscription.Identifier; + var duration = subscription.EndedAt is not null + ? subscription.EndedAt.Value.Minus(subscription.BegunAt).Days.ToString() + : "infinite"; + + await nty.SendNotification( + account, + "subscriptions.begun", + localizer["SubscriptionAppliedTitle", humanReadableName], + null, + localizer["SubscriptionAppliedBody", duration, humanReadableName], + new Dictionary() + { + ["subscription_id"] = subscription.Id.ToString(), + } + ); + } + + private const string SubscriptionCacheKeyPrefix = "subscription:"; + + public async Task GetSubscriptionAsync(Guid accountId, params string[] identifiers) + { + // Create a unique cache key for this subscription + var cacheKey = $"{SubscriptionCacheKeyPrefix}{accountId}:{string.Join(",", identifiers)}"; + + // Try to get the subscription from cache first + var (found, cachedSubscription) = await cache.GetAsyncWithStatus(cacheKey); + if (found && cachedSubscription != null) + { + return cachedSubscription; + } + + // If not in cache, get from database + var subscription = await db.WalletSubscriptions + .Where(s => s.AccountId == accountId && identifiers.Contains(s.Identifier)) + .OrderByDescending(s => s.BegunAt) + .FirstOrDefaultAsync(); + + // Cache the result if found (with 30 minutes expiry) + if (subscription != null) + await cache.SetAsync(cacheKey, subscription, TimeSpan.FromMinutes(30)); + + return subscription; + } +} \ No newline at end of file diff --git a/DysonNetwork.Pass/Wallet/Wallet.cs b/DysonNetwork.Pass/Wallet/Wallet.cs new file mode 100644 index 0000000..2480b0c --- /dev/null +++ b/DysonNetwork.Pass/Wallet/Wallet.cs @@ -0,0 +1,25 @@ +using System.ComponentModel.DataAnnotations; +using System.Text.Json.Serialization; +using DysonNetwork.Shared.Data; + +namespace DysonNetwork.Pass.Wallet; + +public class Wallet : ModelBase +{ + public Guid Id { get; set; } = Guid.NewGuid(); + + public ICollection Pockets { get; set; } = new List(); + + public Guid AccountId { get; set; } + public Account.Account Account { get; set; } = null!; +} + +public class WalletPocket : ModelBase +{ + public Guid Id { get; set; } = Guid.NewGuid(); + [MaxLength(128)] public string Currency { get; set; } = null!; + public decimal Amount { get; set; } + + public Guid WalletId { get; set; } + [JsonIgnore] public Wallet Wallet { get; set; } = null!; +} \ No newline at end of file diff --git a/DysonNetwork.Pass/Wallet/WalletController.cs b/DysonNetwork.Pass/Wallet/WalletController.cs new file mode 100644 index 0000000..70f656e --- /dev/null +++ b/DysonNetwork.Pass/Wallet/WalletController.cs @@ -0,0 +1,101 @@ +using System.ComponentModel.DataAnnotations; +using DysonNetwork.Pass.Permission; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; + +namespace DysonNetwork.Pass.Wallet; + +[ApiController] +[Route("/api/wallets")] +public class WalletController(AppDatabase db, WalletService ws, PaymentService payment) : ControllerBase +{ + [HttpPost] + [Authorize] + public async Task> CreateWallet() + { + if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); + + try + { + var wallet = await ws.CreateWalletAsync(currentUser.Id); + return Ok(wallet); + } + catch (Exception err) + { + return BadRequest(err.Message); + } + } + + [HttpGet] + [Authorize] + public async Task> GetWallet() + { + if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); + + var wallet = await ws.GetWalletAsync(currentUser.Id); + if (wallet is null) return NotFound("Wallet was not found, please create one first."); + return Ok(wallet); + } + + [HttpGet("transactions")] + [Authorize] + public async Task>> GetTransactions( + [FromQuery] int offset = 0, [FromQuery] int take = 20 + ) + { + if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); + + var query = db.PaymentTransactions.AsQueryable() + .Include(t => t.PayeeWallet) + .Include(t => t.PayerWallet) + .Where(t => (t.PayeeWallet != null && t.PayeeWallet.AccountId == currentUser.Id) || + (t.PayerWallet != null && t.PayerWallet.AccountId == currentUser.Id)); + + var transactionCount = await query.CountAsync(); + var transactions = await query + .Skip(offset) + .Take(take) + .OrderByDescending(t => t.CreatedAt) + .ToListAsync(); + + Response.Headers["X-Total"] = transactionCount.ToString(); + + return Ok(transactions); + } + + public class WalletBalanceRequest + { + public string? Remark { get; set; } + [Required] public decimal Amount { get; set; } + [Required] public string Currency { get; set; } = null!; + [Required] public Guid AccountId { get; set; } + } + + [HttpPost("balance")] + [Authorize] + [RequiredPermission("maintenance", "wallets.balance.modify")] + public async Task> ModifyWalletBalance([FromBody] WalletBalanceRequest request) + { + var wallet = await ws.GetWalletAsync(request.AccountId); + if (wallet is null) return NotFound("Wallet was not found."); + + var transaction = request.Amount >= 0 + ? await payment.CreateTransactionAsync( + payerWalletId: null, + payeeWalletId: wallet.Id, + currency: request.Currency, + amount: request.Amount, + remarks: request.Remark + ) + : await payment.CreateTransactionAsync( + payerWalletId: wallet.Id, + payeeWalletId: null, + currency: request.Currency, + amount: request.Amount, + remarks: request.Remark + ); + + return Ok(transaction); + } +} \ No newline at end of file diff --git a/DysonNetwork.Pass/Wallet/WalletService.cs b/DysonNetwork.Pass/Wallet/WalletService.cs new file mode 100644 index 0000000..98d3636 --- /dev/null +++ b/DysonNetwork.Pass/Wallet/WalletService.cs @@ -0,0 +1,49 @@ +using Microsoft.EntityFrameworkCore; + +namespace DysonNetwork.Pass.Wallet; + +public class WalletService(AppDatabase db) +{ + public async Task GetWalletAsync(Guid accountId) + { + return await db.Wallets + .Include(w => w.Pockets) + .FirstOrDefaultAsync(w => w.AccountId == accountId); + } + + public async Task CreateWalletAsync(Guid accountId) + { + var existingWallet = await db.Wallets.FirstOrDefaultAsync(w => w.AccountId == accountId); + if (existingWallet != null) + { + throw new InvalidOperationException($"Wallet already exists for account {accountId}"); + } + + var wallet = new Wallet { AccountId = accountId }; + + db.Wallets.Add(wallet); + await db.SaveChangesAsync(); + + return wallet; + } + + public async Task<(WalletPocket wallet, bool isNewlyCreated)> GetOrCreateWalletPocketAsync( + Guid walletId, + string currency, + decimal? initialAmount = null + ) + { + var pocket = await db.WalletPockets.FirstOrDefaultAsync(p => p.Currency == currency && p.WalletId == walletId); + if (pocket != null) return (pocket, false); + + pocket = new WalletPocket + { + Currency = currency, + Amount = initialAmount ?? 0, + WalletId = walletId + }; + + db.WalletPockets.Add(pocket); + return (pocket, true); + } +} \ No newline at end of file diff --git a/DysonNetwork.Shared/Data/CloudFileReferenceObject.cs b/DysonNetwork.Shared/Data/CloudFileReferenceObject.cs new file mode 100644 index 0000000..ae4054e --- /dev/null +++ b/DysonNetwork.Shared/Data/CloudFileReferenceObject.cs @@ -0,0 +1,17 @@ +namespace DysonNetwork.Shared.Data; + +/// +/// The class that used in jsonb columns which referenced the cloud file. +/// The aim of this class is to store some properties that won't change to a file to reduce the database load. +/// +public class CloudFileReferenceObject : ModelBase, ICloudFile +{ + public string Id { get; set; } = null!; + public string Name { get; set; } = string.Empty; + public Dictionary? FileMeta { get; set; } = null!; + public Dictionary? UserMeta { get; set; } = null!; + public string? MimeType { get; set; } + public string? Hash { get; set; } + public long Size { get; set; } + public bool HasCompression { get; set; } = false; +} \ No newline at end of file diff --git a/DysonNetwork.Shared/Data/ICloudFile.cs b/DysonNetwork.Shared/Data/ICloudFile.cs new file mode 100644 index 0000000..35543a3 --- /dev/null +++ b/DysonNetwork.Shared/Data/ICloudFile.cs @@ -0,0 +1,55 @@ +using NodaTime; + +namespace DysonNetwork.Shared.Data; + +/// +/// Common interface for cloud file entities that can be used in file operations. +/// This interface exposes the essential properties needed for file operations +/// and is implemented by both CloudFile and CloudFileReferenceObject. +/// +public interface ICloudFile +{ + public Instant CreatedAt { get; } + public Instant UpdatedAt { get; } + public Instant? DeletedAt { get; } + + /// + /// Gets the unique identifier of the cloud file. + /// + string Id { get; } + + /// + /// Gets the name of the cloud file. + /// + string Name { get; } + + /// + /// Gets the file metadata dictionary. + /// + Dictionary? FileMeta { get; } + + /// + /// Gets the user metadata dictionary. + /// + Dictionary? UserMeta { get; } + + /// + /// Gets the MIME type of the file. + /// + string? MimeType { get; } + + /// + /// Gets the hash of the file content. + /// + string? Hash { get; } + + /// + /// Gets the size of the file in bytes. + /// + long Size { get; } + + /// + /// Gets whether the file has a compressed version available. + /// + bool HasCompression { get; } +} \ No newline at end of file diff --git a/DysonNetwork.Shared/Data/ModelBase.cs b/DysonNetwork.Shared/Data/ModelBase.cs new file mode 100644 index 0000000..641a19e --- /dev/null +++ b/DysonNetwork.Shared/Data/ModelBase.cs @@ -0,0 +1,15 @@ +using NodaTime; + +namespace DysonNetwork.Shared.Data; + +public interface IIdentifiedResource +{ + public string ResourceIdentifier { get; } +} + +public abstract class ModelBase +{ + public Instant CreatedAt { get; set; } + public Instant UpdatedAt { get; set; } + public Instant? DeletedAt { get; set; } +} From 0318364bcfb493ff52b7cc3d826ddc8a8d0b320e Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Sat, 12 Jul 2025 11:58:38 +0800 Subject: [PATCH 03/42] :recycle: Add services to container in Pass --- DysonNetwork.Pass/DysonNetwork.Pass.csproj | 2 + DysonNetwork.Pass/Program.cs | 43 ++-- .../Startup/ApplicationConfiguration.cs | 66 ++++++ .../Startup/KestrelConfiguration.cs | 17 ++ .../Startup/MetricsConfiguration.cs | 40 ++++ .../Startup/ScheduledJobsConfiguration.cs | 54 +++++ .../Startup/ServiceCollectionExtensions.cs | 195 ++++++++++++++++++ .../DysonNetwork.Sphere.csproj | 4 +- 8 files changed, 406 insertions(+), 15 deletions(-) create mode 100644 DysonNetwork.Pass/Startup/ApplicationConfiguration.cs create mode 100644 DysonNetwork.Pass/Startup/KestrelConfiguration.cs create mode 100644 DysonNetwork.Pass/Startup/MetricsConfiguration.cs create mode 100644 DysonNetwork.Pass/Startup/ScheduledJobsConfiguration.cs create mode 100644 DysonNetwork.Pass/Startup/ServiceCollectionExtensions.cs diff --git a/DysonNetwork.Pass/DysonNetwork.Pass.csproj b/DysonNetwork.Pass/DysonNetwork.Pass.csproj index 35dd6df..3548976 100644 --- a/DysonNetwork.Pass/DysonNetwork.Pass.csproj +++ b/DysonNetwork.Pass/DysonNetwork.Pass.csproj @@ -33,6 +33,8 @@ + + diff --git a/DysonNetwork.Pass/Program.cs b/DysonNetwork.Pass/Program.cs index 666a9c5..9e20295 100644 --- a/DysonNetwork.Pass/Program.cs +++ b/DysonNetwork.Pass/Program.cs @@ -1,23 +1,40 @@ +using DysonNetwork.Pass; +using DysonNetwork.Pass.Startup; +using Microsoft.EntityFrameworkCore; + var builder = WebApplication.CreateBuilder(args); -// Add services to the container. +// Configure Kestrel and server options +builder.ConfigureAppKestrel(); -builder.Services.AddControllers(); -// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi -builder.Services.AddOpenApi(); +// Add metrics and telemetry +builder.Services.AddAppMetrics(); + +// Add application services +builder.Services.AddAppServices(builder.Configuration); +builder.Services.AddAppRateLimiting(); +builder.Services.AddAppAuthentication(); +builder.Services.AddAppSwagger(); + +// Add flush handlers and websocket handlers +builder.Services.AddAppFlushHandlers(); + +// Add business services +builder.Services.AddAppBusinessServices(builder.Configuration); + +// Add scheduled jobs +builder.Services.AddAppScheduledJobs(); var app = builder.Build(); -// Configure the HTTP request pipeline. -if (app.Environment.IsDevelopment()) +// Run database migrations +using (var scope = app.Services.CreateScope()) { - app.MapOpenApi(); + var db = scope.ServiceProvider.GetRequiredService(); + await db.Database.MigrateAsync(); } -app.UseHttpsRedirection(); +// Configure application middleware pipeline +app.ConfigureAppMiddleware(builder.Configuration); -app.UseAuthorization(); - -app.MapControllers(); - -app.Run(); +app.Run(); \ No newline at end of file diff --git a/DysonNetwork.Pass/Startup/ApplicationConfiguration.cs b/DysonNetwork.Pass/Startup/ApplicationConfiguration.cs new file mode 100644 index 0000000..7895bdf --- /dev/null +++ b/DysonNetwork.Pass/Startup/ApplicationConfiguration.cs @@ -0,0 +1,66 @@ +using System.Net; +using DysonNetwork.Pass.Permission; +using Microsoft.AspNetCore.HttpOverrides; +using Prometheus; + +namespace DysonNetwork.Pass.Startup; + +public static class ApplicationConfiguration +{ + public static WebApplication ConfigureAppMiddleware(this WebApplication app, IConfiguration configuration) + { + app.MapMetrics(); + app.MapOpenApi(); + + app.UseSwagger(); + app.UseSwaggerUI(); + + app.UseRequestLocalization(); + + ConfigureForwardedHeaders(app, configuration); + + app.UseCors(opts => + opts.SetIsOriginAllowed(_ => true) + .WithExposedHeaders("*") + .WithHeaders() + .AllowCredentials() + .AllowAnyHeader() + .AllowAnyMethod() + ); + + app.UseWebSockets(); + app.UseRateLimiter(); + app.UseHttpsRedirection(); + app.UseAuthentication(); + app.UseAuthorization(); + app.UseMiddleware(); + + app.MapControllers().RequireRateLimiting("fixed"); + app.MapStaticAssets().RequireRateLimiting("fixed"); + app.MapRazorPages().RequireRateLimiting("fixed"); + + return app; + } + + private static void ConfigureForwardedHeaders(WebApplication app, IConfiguration configuration) + { + var knownProxiesSection = configuration.GetSection("KnownProxies"); + var forwardedHeadersOptions = new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.All }; + + if (knownProxiesSection.Exists()) + { + var proxyAddresses = knownProxiesSection.Get(); + if (proxyAddresses != null) + foreach (var proxy in proxyAddresses) + if (IPAddress.TryParse(proxy, out var ipAddress)) + forwardedHeadersOptions.KnownProxies.Add(ipAddress); + } + else + { + forwardedHeadersOptions.KnownProxies.Add(IPAddress.Any); + forwardedHeadersOptions.KnownProxies.Add(IPAddress.IPv6Any); + } + + app.UseForwardedHeaders(forwardedHeadersOptions); + } +} diff --git a/DysonNetwork.Pass/Startup/KestrelConfiguration.cs b/DysonNetwork.Pass/Startup/KestrelConfiguration.cs new file mode 100644 index 0000000..b042534 --- /dev/null +++ b/DysonNetwork.Pass/Startup/KestrelConfiguration.cs @@ -0,0 +1,17 @@ +namespace DysonNetwork.Pass.Startup; + +public static class KestrelConfiguration +{ + public static WebApplicationBuilder ConfigureAppKestrel(this WebApplicationBuilder builder) + { + builder.Host.UseContentRoot(Directory.GetCurrentDirectory()); + builder.WebHost.ConfigureKestrel(options => + { + options.Limits.MaxRequestBodySize = 50 * 1024 * 1024; + options.Limits.KeepAliveTimeout = TimeSpan.FromMinutes(2); + options.Limits.RequestHeadersTimeout = TimeSpan.FromSeconds(30); + }); + + return builder; + } +} diff --git a/DysonNetwork.Pass/Startup/MetricsConfiguration.cs b/DysonNetwork.Pass/Startup/MetricsConfiguration.cs new file mode 100644 index 0000000..78e33cd --- /dev/null +++ b/DysonNetwork.Pass/Startup/MetricsConfiguration.cs @@ -0,0 +1,40 @@ +using OpenTelemetry.Metrics; +using OpenTelemetry.Trace; +using Prometheus; +using Prometheus.SystemMetrics; + +namespace DysonNetwork.Pass.Startup; + +public static class MetricsConfiguration +{ + public static IServiceCollection AddAppMetrics(this IServiceCollection services) + { + // Prometheus + services.UseHttpClientMetrics(); + services.AddHealthChecks(); + services.AddSystemMetrics(); + services.AddPrometheusEntityFrameworkMetrics(); + services.AddPrometheusAspNetCoreMetrics(); + services.AddPrometheusHttpClientMetrics(); + + // OpenTelemetry + services.AddOpenTelemetry() + .WithTracing(tracing => + { + tracing + .AddAspNetCoreInstrumentation() + .AddHttpClientInstrumentation() + .AddOtlpExporter(); + }) + .WithMetrics(metrics => + { + metrics + .AddAspNetCoreInstrumentation() + .AddHttpClientInstrumentation() + .AddRuntimeInstrumentation() + .AddOtlpExporter(); + }); + + return services; + } +} diff --git a/DysonNetwork.Pass/Startup/ScheduledJobsConfiguration.cs b/DysonNetwork.Pass/Startup/ScheduledJobsConfiguration.cs new file mode 100644 index 0000000..92c7b48 --- /dev/null +++ b/DysonNetwork.Pass/Startup/ScheduledJobsConfiguration.cs @@ -0,0 +1,54 @@ +using DysonNetwork.Pass.Handlers; +using DysonNetwork.Pass.Wallet; +using Quartz; + +namespace DysonNetwork.Pass.Startup; + +public static class ScheduledJobsConfiguration +{ + public static IServiceCollection AddAppScheduledJobs(this IServiceCollection services) + { + services.AddQuartz(q => + { + var appDatabaseRecyclingJob = new JobKey("AppDatabaseRecycling"); + q.AddJob(opts => opts.WithIdentity(appDatabaseRecyclingJob)); + q.AddTrigger(opts => opts + .ForJob(appDatabaseRecyclingJob) + .WithIdentity("AppDatabaseRecyclingTrigger") + .WithCronSchedule("0 0 0 * * ?")); + + var actionLogFlushJob = new JobKey("ActionLogFlush"); + q.AddJob(opts => opts.WithIdentity(actionLogFlushJob)); + q.AddTrigger(opts => opts + .ForJob(actionLogFlushJob) + .WithIdentity("ActionLogFlushTrigger") + .WithSimpleSchedule(o => o + .WithIntervalInMinutes(5) + .RepeatForever()) + ); + + var lastActiveFlushJob = new JobKey("LastActiveFlush"); + q.AddJob(opts => opts.WithIdentity(lastActiveFlushJob)); + q.AddTrigger(opts => opts + .ForJob(lastActiveFlushJob) + .WithIdentity("LastActiveFlushTrigger") + .WithSimpleSchedule(o => o + .WithIntervalInMinutes(5) + .RepeatForever()) + ); + + var subscriptionRenewalJob = new JobKey("SubscriptionRenewal"); + q.AddJob(opts => opts.WithIdentity(subscriptionRenewalJob)); + q.AddTrigger(opts => opts + .ForJob(subscriptionRenewalJob) + .WithIdentity("SubscriptionRenewalTrigger") + .WithSimpleSchedule(o => o + .WithIntervalInMinutes(30) + .RepeatForever()) + ); + }); + services.AddQuartzHostedService(q => q.WaitForJobsToComplete = true); + + return services; + } +} diff --git a/DysonNetwork.Pass/Startup/ServiceCollectionExtensions.cs b/DysonNetwork.Pass/Startup/ServiceCollectionExtensions.cs new file mode 100644 index 0000000..512b18d --- /dev/null +++ b/DysonNetwork.Pass/Startup/ServiceCollectionExtensions.cs @@ -0,0 +1,195 @@ +using System.Globalization; +using DysonNetwork.Pass.Account; +using DysonNetwork.Pass.Auth; +using DysonNetwork.Pass.Auth.OpenId; +using DysonNetwork.Pass.Email; +using DysonNetwork.Pass.Localization; +using DysonNetwork.Pass.Permission; +using DysonNetwork.Pass.Wallet; +using Microsoft.AspNetCore.RateLimiting; +using Microsoft.OpenApi.Models; +using NodaTime; +using NodaTime.Serialization.SystemTextJson; +using StackExchange.Redis; +using System.Text.Json; +using System.Threading.RateLimiting; +using DysonNetwork.Pass.Auth.OidcProvider.Options; +using DysonNetwork.Pass.Auth.OidcProvider.Services; +using DysonNetwork.Pass.Handlers; +using DysonNetwork.Pass.Wallet.PaymentHandlers; +using DysonNetwork.Shared.Cache; +using DysonNetwork.Shared.Geo; + +namespace DysonNetwork.Pass.Startup; + +public static class ServiceCollectionExtensions +{ + public static IServiceCollection AddAppServices(this IServiceCollection services, IConfiguration configuration) + { + services.AddLocalization(options => options.ResourcesPath = "Resources"); + + services.AddDbContext(); + services.AddSingleton(_ => + { + var connection = configuration.GetConnectionString("FastRetrieve")!; + return ConnectionMultiplexer.Connect(connection); + }); + services.AddSingleton(SystemClock.Instance); + services.AddHttpContextAccessor(); + services.AddSingleton(); + + services.AddHttpClient(); + + // Register OIDC services + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + + services.AddControllers().AddJsonOptions(options => + { + options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower; + options.JsonSerializerOptions.DictionaryKeyPolicy = JsonNamingPolicy.SnakeCaseLower; + + options.JsonSerializerOptions.ConfigureForNodaTime(DateTimeZoneProviders.Tzdb); + }).AddDataAnnotationsLocalization(options => + { + options.DataAnnotationLocalizerProvider = (type, factory) => + factory.Create(typeof(SharedResource)); + }); + services.AddRazorPages(); + + services.Configure(options => + { + var supportedCultures = new[] + { + new CultureInfo("en-US"), + new CultureInfo("zh-Hans"), + }; + + options.SupportedCultures = supportedCultures; + options.SupportedUICultures = supportedCultures; + }); + + return services; + } + + public static IServiceCollection AddAppRateLimiting(this IServiceCollection services) + { + services.AddRateLimiter(o => o.AddFixedWindowLimiter(policyName: "fixed", opts => + { + opts.Window = TimeSpan.FromMinutes(1); + opts.PermitLimit = 120; + opts.QueueLimit = 2; + opts.QueueProcessingOrder = QueueProcessingOrder.OldestFirst; + })); + + return services; + } + + public static IServiceCollection AddAppAuthentication(this IServiceCollection services) + { + services.AddCors(); + services.AddAuthorization(); + services.AddAuthentication(options => + { + options.DefaultAuthenticateScheme = AuthConstants.SchemeName; + options.DefaultChallengeScheme = AuthConstants.SchemeName; + }) + .AddScheme(AuthConstants.SchemeName, _ => { }); + + return services; + } + + public static IServiceCollection AddAppSwagger(this IServiceCollection services) + { + services.AddEndpointsApiExplorer(); + services.AddSwaggerGen(options => + { + options.SwaggerDoc("v1", new OpenApiInfo + { + Version = "v1", + Title = "Solar Network API", + Description = "An open-source social network", + TermsOfService = new Uri("https://solsynth.dev/terms"), + License = new OpenApiLicense + { + Name = "APGLv3", + Url = new Uri("https://www.gnu.org/licenses/agpl-3.0.html") + } + }); + options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme + { + In = ParameterLocation.Header, + Description = "Please enter a valid token", + Name = "Authorization", + Type = SecuritySchemeType.Http, + BearerFormat = "JWT", + Scheme = "Bearer" + }); + options.AddSecurityRequirement(new OpenApiSecurityRequirement + { + { + new OpenApiSecurityScheme + { + Reference = new OpenApiReference + { + Type = ReferenceType.SecurityScheme, + Id = "Bearer" + } + }, + [] + } + }); + }); + services.AddOpenApi(); + + return services; + } + + public static IServiceCollection AddAppFlushHandlers(this IServiceCollection services) + { + services.AddSingleton(); + services.AddScoped(); + services.AddScoped(); + + return services; + } + + public static IServiceCollection AddAppBusinessServices(this IServiceCollection services, + IConfiguration configuration) + { + services.AddScoped(); + services.AddScoped(); + services.Configure(configuration.GetSection("GeoIP")); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + + services.Configure(configuration.GetSection("OidcProvider")); + services.AddScoped(); + + return services; + } +} \ No newline at end of file diff --git a/DysonNetwork.Sphere/DysonNetwork.Sphere.csproj b/DysonNetwork.Sphere/DysonNetwork.Sphere.csproj index f7c5de9..72aa5d7 100644 --- a/DysonNetwork.Sphere/DysonNetwork.Sphere.csproj +++ b/DysonNetwork.Sphere/DysonNetwork.Sphere.csproj @@ -71,8 +71,8 @@ - - + + From 33f56c4ef5a663f0e1972eec6febbc5ba7c670c7 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Sat, 12 Jul 2025 15:19:31 +0800 Subject: [PATCH 04/42] :bricks: Grpc service basis --- DysonNetwork.Pass/Account/Account.cs | 4 +- .../Account/AccountCurrentController.cs | 2 +- DysonNetwork.Pass/Account/AccountService.cs | 4 +- .../Account/AccountServiceGrpc.cs | 303 +++++++++++++ DysonNetwork.Pass/AppDatabase.cs | 2 +- DysonNetwork.Pass/DysonNetwork.Pass.csproj | 2 + DysonNetwork.Pass/DysonNetwork.Pass.http | 6 - DysonNetwork.Pass/Program.cs | 4 + .../Startup/ApplicationConfiguration.cs | 8 + .../Startup/ServiceCollectionExtensions.cs | 14 + .../appsettings.Development.json | 8 - DysonNetwork.Pass/appsettings.json | 122 ++++- .../Connection/ClientTypeMiddleware.cs | 42 ++ .../Connection/IWebSocketPacketHandler.cs | 9 + .../Connection/WebSocketController.cs | 108 +++++ .../Connection/WebSocketPacket.cs | 72 +++ .../Connection/WebSocketService.cs | 129 ++++++ DysonNetwork.Pusher/Dockerfile | 23 + .../DysonNetwork.Pusher.csproj | 21 + DysonNetwork.Pusher/Program.cs | 23 + .../Properties/launchSettings.json | 23 + DysonNetwork.Pusher/appsettings.json | 129 ++++++ .../Data/CloudFileReferenceObject.cs | 41 ++ .../DysonNetwork.Shared.csproj | 28 +- DysonNetwork.Shared/Proto/account.proto | 427 ++++++++++++++++++ DysonNetwork.Shared/Proto/file.proto | 87 ++++ .../DysonNetwork.Sphere.csproj | 1 + DysonNetwork.sln | 6 + 28 files changed, 1620 insertions(+), 28 deletions(-) create mode 100644 DysonNetwork.Pass/Account/AccountServiceGrpc.cs delete mode 100644 DysonNetwork.Pass/DysonNetwork.Pass.http delete mode 100644 DysonNetwork.Pass/appsettings.Development.json create mode 100644 DysonNetwork.Pusher/Connection/ClientTypeMiddleware.cs create mode 100644 DysonNetwork.Pusher/Connection/IWebSocketPacketHandler.cs create mode 100644 DysonNetwork.Pusher/Connection/WebSocketController.cs create mode 100644 DysonNetwork.Pusher/Connection/WebSocketPacket.cs create mode 100644 DysonNetwork.Pusher/Connection/WebSocketService.cs create mode 100644 DysonNetwork.Pusher/Dockerfile create mode 100644 DysonNetwork.Pusher/DysonNetwork.Pusher.csproj create mode 100644 DysonNetwork.Pusher/Program.cs create mode 100644 DysonNetwork.Pusher/Properties/launchSettings.json create mode 100644 DysonNetwork.Pusher/appsettings.json create mode 100644 DysonNetwork.Shared/Proto/account.proto create mode 100644 DysonNetwork.Shared/Proto/file.proto diff --git a/DysonNetwork.Pass/Account/Account.cs b/DysonNetwork.Pass/Account/Account.cs index 027e127..a4183aa 100644 --- a/DysonNetwork.Pass/Account/Account.cs +++ b/DysonNetwork.Pass/Account/Account.cs @@ -18,7 +18,7 @@ public class Account : ModelBase public Instant? ActivatedAt { get; set; } public bool IsSuperuser { get; set; } = false; - public Profile Profile { get; set; } = null!; + public AccountProfile Profile { get; set; } = null!; public ICollection Contacts { get; set; } = new List(); public ICollection Badges { get; set; } = new List(); @@ -53,7 +53,7 @@ public abstract class Leveling ]; } -public class Profile : ModelBase +public class AccountProfile : ModelBase { public Guid Id { get; set; } [MaxLength(256)] public string? FirstName { get; set; } diff --git a/DysonNetwork.Pass/Account/AccountCurrentController.cs b/DysonNetwork.Pass/Account/AccountCurrentController.cs index 037935d..f5e830a 100644 --- a/DysonNetwork.Pass/Account/AccountCurrentController.cs +++ b/DysonNetwork.Pass/Account/AccountCurrentController.cs @@ -72,7 +72,7 @@ public class AccountCurrentController( } [HttpPatch("profile")] - public async Task> UpdateProfile([FromBody] ProfileRequest request) + public async Task> UpdateProfile([FromBody] ProfileRequest request) { if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); var userId = currentUser.Id; diff --git a/DysonNetwork.Pass/Account/AccountService.cs b/DysonNetwork.Pass/Account/AccountService.cs index 6ba2919..f376468 100644 --- a/DysonNetwork.Pass/Account/AccountService.cs +++ b/DysonNetwork.Pass/Account/AccountService.cs @@ -115,7 +115,7 @@ public class AccountService( }.HashSecret() } : [], - Profile = new Profile() + Profile = new AccountProfile() }; if (isActivated) @@ -648,7 +648,7 @@ public class AccountService( if (missingId.Count != 0) { - var newProfiles = missingId.Select(id => new Profile { Id = Guid.NewGuid(), AccountId = id }).ToList(); + var newProfiles = missingId.Select(id => new AccountProfile { Id = Guid.NewGuid(), AccountId = id }).ToList(); await db.BulkInsertAsync(newProfiles); } } diff --git a/DysonNetwork.Pass/Account/AccountServiceGrpc.cs b/DysonNetwork.Pass/Account/AccountServiceGrpc.cs new file mode 100644 index 0000000..9ccbac7 --- /dev/null +++ b/DysonNetwork.Pass/Account/AccountServiceGrpc.cs @@ -0,0 +1,303 @@ +using DysonNetwork.Shared.Proto; +using Google.Protobuf.WellKnownTypes; +using Grpc.Core; +using Microsoft.EntityFrameworkCore; +using NodaTime; +using NodaTime.Serialization.Protobuf; + +namespace DysonNetwork.Pass.Account; + +public class AccountServiceGrpc( + AppDatabase db, + IClock clock, + ILogger logger +) + : Shared.Proto.AccountService.AccountServiceBase +{ + private readonly AppDatabase _db = db ?? throw new ArgumentNullException(nameof(db)); + private readonly IClock _clock = clock ?? throw new ArgumentNullException(nameof(clock)); + + private readonly ILogger + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + + // Helper methods for conversion between protobuf and domain models + private static Shared.Proto.Account ToProtoAccount(Account account) => new() + { + Id = account.Id.ToString(), + Name = account.Name, + Nick = account.Nick, + Language = account.Language, + ActivatedAt = account.ActivatedAt?.ToTimestamp(), + IsSuperuser = account.IsSuperuser, + Profile = ToProtoProfile(account.Profile) + // Note: Collections are not included by default to avoid large payloads + // They should be loaded on demand via specific methods + }; + + private static Shared.Proto.AccountProfile ToProtoProfile(AccountProfile profile) => new() + { + Id = profile.Id.ToString(), + FirstName = profile.FirstName, + MiddleName = profile.MiddleName, + LastName = profile.LastName, + Bio = profile.Bio, + Gender = profile.Gender, + Pronouns = profile.Pronouns, + TimeZone = profile.TimeZone, + Location = profile.Location, + Birthday = profile.Birthday?.ToTimestamp(), + LastSeenAt = profile.LastSeenAt?.ToTimestamp(), + Experience = profile.Experience, + Level = profile.Level, + LevelingProgress = profile.LevelingProgress, + AccountId = profile.AccountId.ToString(), + PictureId = profile.PictureId, + BackgroundId = profile.BackgroundId, + Picture = profile.Picture?.ToProtoValue(), + Background = profile.Background?.ToProtoValue() + }; + + private static Shared.Proto.AccountContact ToProtoContact(AccountContact contact) => new() + { + Id = contact.Id.ToString(), + Type = contact.Type switch + { + AccountContactType.Address => Shared.Proto.AccountContactType.Address, + AccountContactType.PhoneNumber => Shared.Proto.AccountContactType.PhoneNumber, + AccountContactType.Email => Shared.Proto.AccountContactType.Email, + _ => Shared.Proto.AccountContactType.Unspecified + }, + VerifiedAt = contact.VerifiedAt?.ToTimestamp(), + IsPrimary = contact.IsPrimary, + Content = contact.Content, + AccountId = contact.AccountId.ToString() + }; + + private static Shared.Proto.AccountBadge ToProtoBadge(AccountBadge badge) => new() + { + Id = badge.Id.ToString(), + Type = badge.Type, + Label = badge.Label, + Caption = badge.Caption, + ActivatedAt = badge.ActivatedAt?.ToTimestamp(), + ExpiredAt = badge.ExpiredAt?.ToTimestamp(), + AccountId = badge.AccountId.ToString() + }; + +// Implementation of gRPC service methods + public override async Task GetAccount(GetAccountRequest request, ServerCallContext context) + { + if (!Guid.TryParse(request.Id, out var accountId)) + throw new RpcException(new Grpc.Core.Status(StatusCode.InvalidArgument, "Invalid account ID format")); + + var account = await _db.Accounts + .AsNoTracking() + .Include(a => a.Profile) + .FirstOrDefaultAsync(a => a.Id == accountId); + + if (account == null) + throw new RpcException(new Grpc.Core.Status(StatusCode.NotFound, $"Account {request.Id} not found")); + + return ToProtoAccount(account); + } + + public override async Task CreateAccount(CreateAccountRequest request, + ServerCallContext context) + { + // Map protobuf request to domain model + var account = new Account + { + Name = request.Name, + Nick = request.Nick, + Language = request.Language, + IsSuperuser = request.IsSuperuser, + ActivatedAt = request.Profile != null ? null : _clock.GetCurrentInstant(), + Profile = new AccountProfile + { + FirstName = request.Profile?.FirstName, + LastName = request.Profile?.LastName, + // Initialize other profile fields as needed + } + }; + + // Add to database + _db.Accounts.Add(account); + await _db.SaveChangesAsync(); + + _logger.LogInformation("Created new account with ID {AccountId}", account.Id); + return ToProtoAccount(account); + } + + public override async Task UpdateAccount(UpdateAccountRequest request, + ServerCallContext context) + { + if (!Guid.TryParse(request.Id, out var accountId)) + throw new RpcException(new Grpc.Core.Status(StatusCode.InvalidArgument, "Invalid account ID format")); + + var account = await _db.Accounts.FindAsync(accountId); + if (account == null) + throw new RpcException(new Grpc.Core.Status(StatusCode.NotFound, $"Account {request.Id} not found")); + + // Update fields if they are provided in the request + if (request.Name != null) account.Name = request.Name; + if (request.Nick != null) account.Nick = request.Nick; + if (request.Language != null) account.Language = request.Language; + if (request.IsSuperuser != null) account.IsSuperuser = request.IsSuperuser.Value; + + await _db.SaveChangesAsync(); + return ToProtoAccount(account); + } + + public override async Task DeleteAccount(DeleteAccountRequest request, ServerCallContext context) + { + if (!Guid.TryParse(request.Id, out var accountId)) + throw new RpcException(new Grpc.Core.Status(StatusCode.InvalidArgument, "Invalid account ID format")); + + var account = await _db.Accounts.FindAsync(accountId); + if (account == null) + throw new RpcException(new Grpc.Core.Status(StatusCode.NotFound, $"Account {request.Id} not found")); + + _db.Accounts.Remove(account); + + await _db.SaveChangesAsync(); + return new Empty(); + } + + public override async Task ListAccounts(ListAccountsRequest request, + ServerCallContext context) + { + var query = _db.Accounts.AsNoTracking(); + + // Apply filters if provided + if (!string.IsNullOrEmpty(request.Filter)) + { + // Implement filtering logic based on request.Filter + // This is a simplified example + query = query.Where(a => a.Name.Contains(request.Filter) || a.Nick.Contains(request.Filter)); + } + + // Apply ordering + query = request.OrderBy switch + { + "name" => query.OrderBy(a => a.Name), + "name_desc" => query.OrderByDescending(a => a.Name), + _ => query.OrderBy(a => a.Id) + }; + + // Get total count for pagination + var totalCount = await query.CountAsync(); + + // Apply pagination + var accounts = await query + .Skip(request.PageSize * (request.PageToken != null ? int.Parse(request.PageToken) : 0)) + .Take(request.PageSize) + .Include(a => a.Profile) + .ToListAsync(); + + var response = new ListAccountsResponse + { + TotalSize = totalCount, + NextPageToken = (accounts.Count == request.PageSize) + ? ((request.PageToken != null ? int.Parse(request.PageToken) : 0) + 1).ToString() + : "" + }; + + response.Accounts.AddRange(accounts.Select(ToProtoAccount)); + return response; + } + +// Implement other service methods following the same pattern... + +// Profile operations + public override async Task GetProfile(GetProfileRequest request, + ServerCallContext context) + { + if (!Guid.TryParse(request.AccountId, out var accountId)) + throw new RpcException(new Grpc.Core.Status(StatusCode.InvalidArgument, "Invalid account ID format")); + + var profile = await _db.AccountProfiles + .AsNoTracking() + .FirstOrDefaultAsync(p => p.AccountId == accountId); + + if (profile == null) + throw new RpcException(new Grpc.Core.Status(StatusCode.NotFound, + $"Profile for account {request.AccountId} not found")); + + return ToProtoProfile(profile); + } + + public override async Task UpdateProfile(UpdateProfileRequest request, + ServerCallContext context) + { + if (!Guid.TryParse(request.AccountId, out var accountId)) + throw new RpcException(new Grpc.Core.Status(StatusCode.InvalidArgument, "Invalid account ID format")); + + var profile = await _db.AccountProfiles + .FirstOrDefaultAsync(p => p.AccountId == accountId); + + if (profile == null) + throw new RpcException(new Grpc.Core.Status(StatusCode.NotFound, + $"Profile for account {request.AccountId} not found")); + + // Update only the fields specified in the field mask + if (request.UpdateMask == null || request.UpdateMask.Paths.Contains("first_name")) + profile.FirstName = request.Profile.FirstName; + + if (request.UpdateMask == null || request.UpdateMask.Paths.Contains("last_name")) + profile.LastName = request.Profile.LastName; + + // Update other fields similarly... + + await _db.SaveChangesAsync(); + return ToProtoProfile(profile); + } + +// Contact operations + public override async Task AddContact(AddContactRequest request, + ServerCallContext context) + { + if (!Guid.TryParse(request.AccountId, out var accountId)) + throw new RpcException(new Grpc.Core.Status(StatusCode.InvalidArgument, "Invalid account ID format")); + + var contact = new AccountContact + { + AccountId = accountId, + Type = (AccountContactType)request.Type, + Content = request.Content, + IsPrimary = request.IsPrimary, + VerifiedAt = null + }; + + _db.AccountContacts.Add(contact); + await _db.SaveChangesAsync(); + + return ToProtoContact(contact); + } + +// Implement other contact operations... + +// Badge operations + public override async Task AddBadge(AddBadgeRequest request, ServerCallContext context) + { + if (!Guid.TryParse(request.AccountId, out var accountId)) + throw new RpcException(new Grpc.Core.Status(StatusCode.InvalidArgument, "Invalid account ID format")); + + var badge = new AccountBadge + { + AccountId = accountId, + Type = request.Type, + Label = request.Label, + Caption = request.Caption, + ActivatedAt = _clock.GetCurrentInstant(), + ExpiredAt = request.ExpiredAt?.ToInstant(), + Meta = request.Meta.ToDictionary(kvp => kvp.Key, kvp => (object)kvp.Value) + }; + + _db.Badges.Add(badge); + await _db.SaveChangesAsync(); + + return ToProtoBadge(badge); + } + +// Implement other badge operations... +} \ No newline at end of file diff --git a/DysonNetwork.Pass/AppDatabase.cs b/DysonNetwork.Pass/AppDatabase.cs index 04b8a39..f666e8f 100644 --- a/DysonNetwork.Pass/AppDatabase.cs +++ b/DysonNetwork.Pass/AppDatabase.cs @@ -25,7 +25,7 @@ public class AppDatabase( public DbSet MagicSpells { get; set; } public DbSet Accounts { get; set; } public DbSet AccountConnections { get; set; } - public DbSet AccountProfiles { get; set; } + public DbSet AccountProfiles { get; set; } public DbSet AccountContacts { get; set; } public DbSet AccountAuthFactors { get; set; } public DbSet AccountRelationships { get; set; } diff --git a/DysonNetwork.Pass/DysonNetwork.Pass.csproj b/DysonNetwork.Pass/DysonNetwork.Pass.csproj index 3548976..3b281ce 100644 --- a/DysonNetwork.Pass/DysonNetwork.Pass.csproj +++ b/DysonNetwork.Pass/DysonNetwork.Pass.csproj @@ -7,9 +7,11 @@ + + diff --git a/DysonNetwork.Pass/DysonNetwork.Pass.http b/DysonNetwork.Pass/DysonNetwork.Pass.http deleted file mode 100644 index 067647c..0000000 --- a/DysonNetwork.Pass/DysonNetwork.Pass.http +++ /dev/null @@ -1,6 +0,0 @@ -@DysonNetwork.Pass_HostAddress = http://localhost:5216 - -GET {{DysonNetwork.Pass_HostAddress}}/weatherforecast/ -Accept: application/json - -### diff --git a/DysonNetwork.Pass/Program.cs b/DysonNetwork.Pass/Program.cs index 9e20295..e52a3c3 100644 --- a/DysonNetwork.Pass/Program.cs +++ b/DysonNetwork.Pass/Program.cs @@ -1,4 +1,5 @@ using DysonNetwork.Pass; +using DysonNetwork.Pass.Account; using DysonNetwork.Pass.Startup; using Microsoft.EntityFrameworkCore; @@ -37,4 +38,7 @@ using (var scope = app.Services.CreateScope()) // Configure application middleware pipeline app.ConfigureAppMiddleware(builder.Configuration); +// Configure gRPC +app.ConfigureGrpcServices(); + app.Run(); \ No newline at end of file diff --git a/DysonNetwork.Pass/Startup/ApplicationConfiguration.cs b/DysonNetwork.Pass/Startup/ApplicationConfiguration.cs index 7895bdf..d997ce4 100644 --- a/DysonNetwork.Pass/Startup/ApplicationConfiguration.cs +++ b/DysonNetwork.Pass/Startup/ApplicationConfiguration.cs @@ -1,4 +1,5 @@ using System.Net; +using DysonNetwork.Pass.Account; using DysonNetwork.Pass.Permission; using Microsoft.AspNetCore.HttpOverrides; using Prometheus; @@ -63,4 +64,11 @@ public static class ApplicationConfiguration app.UseForwardedHeaders(forwardedHeadersOptions); } + + public static WebApplication ConfigureGrpcServices(this WebApplication app) + { + app.MapGrpcService(); + + return app; + } } diff --git a/DysonNetwork.Pass/Startup/ServiceCollectionExtensions.cs b/DysonNetwork.Pass/Startup/ServiceCollectionExtensions.cs index 512b18d..eec3b8a 100644 --- a/DysonNetwork.Pass/Startup/ServiceCollectionExtensions.cs +++ b/DysonNetwork.Pass/Startup/ServiceCollectionExtensions.cs @@ -40,6 +40,20 @@ public static class ServiceCollectionExtensions services.AddHttpClient(); + // Register gRPC services + services.AddGrpc(options => + { + options.EnableDetailedErrors = true; // Will be adjusted in Program.cs + options.MaxReceiveMessageSize = 16 * 1024 * 1024; // 16MB + options.MaxSendMessageSize = 16 * 1024 * 1024; // 16MB + }); + + // Register gRPC reflection for service discovery + services.AddGrpc(); + + // Register gRPC services + services.AddScoped(); + // Register OIDC services services.AddScoped(); services.AddScoped(); diff --git a/DysonNetwork.Pass/appsettings.Development.json b/DysonNetwork.Pass/appsettings.Development.json deleted file mode 100644 index 0c208ae..0000000 --- a/DysonNetwork.Pass/appsettings.Development.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft.AspNetCore": "Warning" - } - } -} diff --git a/DysonNetwork.Pass/appsettings.json b/DysonNetwork.Pass/appsettings.json index 10f68b8..82c1088 100644 --- a/DysonNetwork.Pass/appsettings.json +++ b/DysonNetwork.Pass/appsettings.json @@ -1,9 +1,129 @@ { + "Debug": true, + "BaseUrl": "http://localhost:5071", "Logging": { "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Warning" } }, - "AllowedHosts": "*" + "AllowedHosts": "*", + "ConnectionStrings": { + "App": "Host=localhost;Port=5432;Database=dyson_network;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60", + "FastRetrieve": "localhost:6379" + }, + "Authentication": { + "Schemes": { + "Bearer": { + "ValidAudiences": [ + "http://localhost:5071", + "https://localhost:7099" + ], + "ValidIssuer": "solar-network" + } + } + }, + "AuthToken": { + "PublicKeyPath": "Keys/PublicKey.pem", + "PrivateKeyPath": "Keys/PrivateKey.pem" + }, + "OidcProvider": { + "IssuerUri": "https://nt.solian.app", + "PublicKeyPath": "Keys/PublicKey.pem", + "PrivateKeyPath": "Keys/PrivateKey.pem", + "AccessTokenLifetime": "01:00:00", + "RefreshTokenLifetime": "30.00:00:00", + "AuthorizationCodeLifetime": "00:30:00", + "RequireHttpsMetadata": true + }, + "Tus": { + "StorePath": "Uploads" + }, + "Storage": { + "PreferredRemote": "minio", + "Remote": [ + { + "Id": "minio", + "Label": "Minio", + "Region": "auto", + "Bucket": "solar-network-development", + "Endpoint": "localhost:9000", + "SecretId": "littlesheep", + "SecretKey": "password", + "EnabledSigned": true, + "EnableSsl": false + }, + { + "Id": "cloudflare", + "Label": "Cloudflare R2", + "Region": "auto", + "Bucket": "solar-network", + "Endpoint": "0a70a6d1b7128888c823359d0008f4e1.r2.cloudflarestorage.com", + "SecretId": "8ff5d06c7b1639829d60bc6838a542e6", + "SecretKey": "fd58158c5201be16d1872c9209d9cf199421dae3c2f9972f94b2305976580d67", + "EnableSigned": true, + "EnableSsl": true + } + ] + }, + "Captcha": { + "Provider": "cloudflare", + "ApiKey": "0x4AAAAAABCDUdOujj4feOb_", + "ApiSecret": "0x4AAAAAABCDUWABiJQweqlB7tYq-IqIm8U" + }, + "Notifications": { + "Topic": "dev.solsynth.solian", + "Endpoint": "http://localhost:8088" + }, + "Email": { + "Server": "smtp4dev.orb.local", + "Port": 25, + "UseSsl": false, + "Username": "no-reply@mail.solsynth.dev", + "Password": "password", + "FromAddress": "no-reply@mail.solsynth.dev", + "FromName": "Alphabot", + "SubjectPrefix": "Solar Network" + }, + "RealtimeChat": { + "Endpoint": "https://solar-network-im44o8gq.livekit.cloud", + "ApiKey": "APIs6TiL8wj3A4j", + "ApiSecret": "SffxRneIwTnlHPtEf3zicmmv3LUEl7xXael4PvWZrEhE" + }, + "GeoIp": { + "DatabasePath": "./Keys/GeoLite2-City.mmdb" + }, + "Oidc": { + "Google": { + "ClientId": "961776991058-963m1qin2vtp8fv693b5fdrab5hmpl89.apps.googleusercontent.com", + "ClientSecret": "" + }, + "Apple": { + "ClientId": "dev.solsynth.solian", + "TeamId": "W7HPZ53V6B", + "KeyId": "B668YP4KBG", + "PrivateKeyPath": "./Keys/Solarpass.p8" + }, + "Microsoft": { + "ClientId": "YOUR_MICROSOFT_CLIENT_ID", + "ClientSecret": "YOUR_MICROSOFT_CLIENT_SECRET", + "DiscoveryEndpoint": "YOUR_MICROSOFT_DISCOVERY_ENDPOINT" + } + }, + "Payment": { + "Auth": { + "Afdian": "" + }, + "Subscriptions": { + "Afdian": { + "7d17aae23c9611f0b5705254001e7c00": "solian.stellar.primary", + "7dfae4743c9611f0b3a55254001e7c00": "solian.stellar.nova", + "141713ee3d6211f085b352540025c377": "solian.stellar.supernova" + } + } + }, + "KnownProxies": [ + "127.0.0.1", + "::1" + ] } diff --git a/DysonNetwork.Pusher/Connection/ClientTypeMiddleware.cs b/DysonNetwork.Pusher/Connection/ClientTypeMiddleware.cs new file mode 100644 index 0000000..4e26d43 --- /dev/null +++ b/DysonNetwork.Pusher/Connection/ClientTypeMiddleware.cs @@ -0,0 +1,42 @@ +namespace DysonNetwork.Pusher.Connection; + +public class ClientTypeMiddleware(RequestDelegate next) +{ + public async Task Invoke(HttpContext context) + { + var headers = context.Request.Headers; + bool isWebPage; + + // Priority 1: Check for custom header + if (headers.TryGetValue("X-Client", out var clientType)) + { + isWebPage = clientType.ToString().Length == 0; + } + else + { + var userAgent = headers.UserAgent.ToString(); + var accept = headers.Accept.ToString(); + + // Priority 2: Check known app User-Agent (backward compatibility) + if (!string.IsNullOrEmpty(userAgent) && userAgent.Contains("Solian")) + isWebPage = false; + // Priority 3: Accept header can help infer intent + else if (!string.IsNullOrEmpty(accept) && accept.Contains("text/html")) + isWebPage = true; + else if (!string.IsNullOrEmpty(accept) && accept.Contains("application/json")) + isWebPage = false; + else + isWebPage = true; + } + + context.Items["IsWebPage"] = isWebPage; + + if (!isWebPage && context.Request.Path != "/ws" && !context.Request.Path.StartsWithSegments("/api")) + context.Response.Redirect( + $"/api{context.Request.Path.Value}{context.Request.QueryString.Value}", + permanent: false + ); + else + await next(context); + } +} \ No newline at end of file diff --git a/DysonNetwork.Pusher/Connection/IWebSocketPacketHandler.cs b/DysonNetwork.Pusher/Connection/IWebSocketPacketHandler.cs new file mode 100644 index 0000000..0d5c591 --- /dev/null +++ b/DysonNetwork.Pusher/Connection/IWebSocketPacketHandler.cs @@ -0,0 +1,9 @@ +using System.Net.WebSockets; + +namespace DysonNetwork.Pusher.Connection; + +public interface IWebSocketPacketHandler +{ + string PacketType { get; } + Task HandleAsync(Account currentUser, string deviceId, WebSocketPacket packet, WebSocket socket, WebSocketService srv); +} \ No newline at end of file diff --git a/DysonNetwork.Pusher/Connection/WebSocketController.cs b/DysonNetwork.Pusher/Connection/WebSocketController.cs new file mode 100644 index 0000000..8f81f04 --- /dev/null +++ b/DysonNetwork.Pusher/Connection/WebSocketController.cs @@ -0,0 +1,108 @@ +using System.Collections.Concurrent; +using System.Net.WebSockets; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore.Metadata.Internal; +using Swashbuckle.AspNetCore.Annotations; + +namespace DysonNetwork.Pusher.Connection; + +[ApiController] +[Route("/ws")] +public class WebSocketController(WebSocketService ws, ILogger logger) : ControllerBase +{ + [Route("/ws")] + [Authorize] + [SwaggerIgnore] + public async Task TheGateway() + { + HttpContext.Items.TryGetValue("CurrentUser", out var currentUserValue); + HttpContext.Items.TryGetValue("CurrentSession", out var currentSessionValue); + if (currentUserValue is not Account.Account currentUser || + currentSessionValue is not Auth.Session currentSession) + { + HttpContext.Response.StatusCode = StatusCodes.Status401Unauthorized; + return; + } + + var accountId = currentUser.Id; + var deviceId = currentSession.Challenge.DeviceId; + + if (string.IsNullOrEmpty(deviceId)) + { + HttpContext.Response.StatusCode = StatusCodes.Status400BadRequest; + return; + } + + using var webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync(); + var cts = new CancellationTokenSource(); + var connectionKey = (accountId, deviceId); + + if (!ws.TryAdd(connectionKey, webSocket, cts)) + { + await webSocket.CloseAsync( + WebSocketCloseStatus.InternalServerError, + "Failed to establish connection.", + CancellationToken.None + ); + return; + } + + logger.LogInformation( + $"Connection established with user @{currentUser.Name}#{currentUser.Id} and device #{deviceId}"); + + try + { + await _ConnectionEventLoop(deviceId, currentUser, webSocket, cts.Token); + } + catch (Exception ex) + { + Console.WriteLine($"WebSocket Error: {ex.Message}"); + } + finally + { + ws.Disconnect(connectionKey); + logger.LogInformation( + $"Connection disconnected with user @{currentUser.Name}#{currentUser.Id} and device #{deviceId}"); + } + } + + private async Task _ConnectionEventLoop( + string deviceId, + Account.Account currentUser, + WebSocket webSocket, + CancellationToken cancellationToken + ) + { + var connectionKey = (AccountId: currentUser.Id, DeviceId: deviceId); + + var buffer = new byte[1024 * 4]; + try + { + var receiveResult = await webSocket.ReceiveAsync( + new ArraySegment(buffer), + cancellationToken + ); + while (!receiveResult.CloseStatus.HasValue) + { + receiveResult = await webSocket.ReceiveAsync( + new ArraySegment(buffer), + cancellationToken + ); + + var packet = WebSocketPacket.FromBytes(buffer[..receiveResult.Count]); + _ = ws.HandlePacket(currentUser, connectionKey.DeviceId, packet, webSocket); + } + } + catch (OperationCanceledException) + { + if ( + webSocket.State != WebSocketState.Closed + && webSocket.State != WebSocketState.Aborted + ) + { + ws.Disconnect(connectionKey); + } + } + } +} \ No newline at end of file diff --git a/DysonNetwork.Pusher/Connection/WebSocketPacket.cs b/DysonNetwork.Pusher/Connection/WebSocketPacket.cs new file mode 100644 index 0000000..745a961 --- /dev/null +++ b/DysonNetwork.Pusher/Connection/WebSocketPacket.cs @@ -0,0 +1,72 @@ +using System.Text.Json; +using NodaTime; +using NodaTime.Serialization.SystemTextJson; + +public class WebSocketPacketType +{ + public const string Error = "error"; + public const string MessageNew = "messages.new"; + public const string MessageUpdate = "messages.update"; + public const string MessageDelete = "messages.delete"; + public const string CallParticipantsUpdate = "call.participants.update"; +} + +public class WebSocketPacket +{ + public string Type { get; set; } = null!; + public object Data { get; set; } = null!; + public string? ErrorMessage { get; set; } + + /// + /// Creates a WebSocketPacket from raw WebSocket message bytes + /// + /// Raw WebSocket message bytes + /// Deserialized WebSocketPacket + public static WebSocketPacket FromBytes(byte[] bytes) + { + var json = System.Text.Encoding.UTF8.GetString(bytes); + var jsonOpts = new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower, + DictionaryKeyPolicy = JsonNamingPolicy.SnakeCaseLower, + }; + return JsonSerializer.Deserialize(json, jsonOpts) ?? + throw new JsonException("Failed to deserialize WebSocketPacket"); + } + + /// + /// Deserializes the Data property to the specified type T + /// + /// Target type to deserialize to + /// Deserialized data of type T + public T? GetData() + { + if (Data is T typedData) + return typedData; + + var jsonOpts = new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower, + DictionaryKeyPolicy = JsonNamingPolicy.SnakeCaseLower, + }; + return JsonSerializer.Deserialize( + JsonSerializer.Serialize(Data, jsonOpts), + jsonOpts + ); + } + + /// + /// Serializes this WebSocketPacket to a byte array for sending over WebSocket + /// + /// Byte array representation of the packet + public byte[] ToBytes() + { + var jsonOpts = new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower, + DictionaryKeyPolicy = JsonNamingPolicy.SnakeCaseLower, + }.ConfigureForNodaTime(DateTimeZoneProviders.Tzdb); + var json = JsonSerializer.Serialize(this, jsonOpts); + return System.Text.Encoding.UTF8.GetBytes(json); + } +} \ No newline at end of file diff --git a/DysonNetwork.Pusher/Connection/WebSocketService.cs b/DysonNetwork.Pusher/Connection/WebSocketService.cs new file mode 100644 index 0000000..948db1d --- /dev/null +++ b/DysonNetwork.Pusher/Connection/WebSocketService.cs @@ -0,0 +1,129 @@ +using System.Collections.Concurrent; +using System.Net.WebSockets; + +namespace DysonNetwork.Pusher.Connection; + +public class WebSocketService +{ + private readonly IDictionary _handlerMap; + + public WebSocketService(IEnumerable handlers) + { + _handlerMap = handlers.ToDictionary(h => h.PacketType); + } + + private static readonly ConcurrentDictionary< + (Guid AccountId, string DeviceId), + (WebSocket Socket, CancellationTokenSource Cts) + > ActiveConnections = new(); + + private static readonly ConcurrentDictionary ActiveSubscriptions = new(); // deviceId -> chatRoomId + + public void SubscribeToChatRoom(string chatRoomId, string deviceId) + { + ActiveSubscriptions[deviceId] = chatRoomId; + } + + public void UnsubscribeFromChatRoom(string deviceId) + { + ActiveSubscriptions.TryRemove(deviceId, out _); + } + + public bool IsUserSubscribedToChatRoom(Guid accountId, string chatRoomId) + { + var userDeviceIds = ActiveConnections.Keys.Where(k => k.AccountId == accountId).Select(k => k.DeviceId); + foreach (var deviceId in userDeviceIds) + { + if (ActiveSubscriptions.TryGetValue(deviceId, out var subscribedChatRoomId) && subscribedChatRoomId == chatRoomId) + { + return true; + } + } + return false; + } + + public bool TryAdd( + (Guid AccountId, string DeviceId) key, + WebSocket socket, + CancellationTokenSource cts + ) + { + if (ActiveConnections.TryGetValue(key, out _)) + Disconnect(key, + "Just connected somewhere else with the same identifier."); // Disconnect the previous one using the same identifier + return ActiveConnections.TryAdd(key, (socket, cts)); + } + + public void Disconnect((Guid AccountId, string DeviceId) key, string? reason = null) + { + if (!ActiveConnections.TryGetValue(key, out var data)) return; + data.Socket.CloseAsync( + WebSocketCloseStatus.NormalClosure, + reason ?? "Server just decided to disconnect.", + CancellationToken.None + ); + data.Cts.Cancel(); + ActiveConnections.TryRemove(key, out _); + UnsubscribeFromChatRoom(key.DeviceId); + } + + public bool GetAccountIsConnected(Guid accountId) + { + return ActiveConnections.Any(c => c.Key.AccountId == accountId); + } + + public void SendPacketToAccount(Guid userId, WebSocketPacket packet) + { + var connections = ActiveConnections.Where(c => c.Key.AccountId == userId); + var packetBytes = packet.ToBytes(); + var segment = new ArraySegment(packetBytes); + + foreach (var connection in connections) + { + connection.Value.Socket.SendAsync( + segment, + WebSocketMessageType.Binary, + true, + CancellationToken.None + ); + } + } + + public void SendPacketToDevice(string deviceId, WebSocketPacket packet) + { + var connections = ActiveConnections.Where(c => c.Key.DeviceId == deviceId); + var packetBytes = packet.ToBytes(); + var segment = new ArraySegment(packetBytes); + + foreach (var connection in connections) + { + connection.Value.Socket.SendAsync( + segment, + WebSocketMessageType.Binary, + true, + CancellationToken.None + ); + } + } + + public async Task HandlePacket(Account.Account currentUser, string deviceId, WebSocketPacket packet, + WebSocket socket) + { + if (_handlerMap.TryGetValue(packet.Type, out var handler)) + { + await handler.HandleAsync(currentUser, deviceId, packet, socket, this); + return; + } + + await socket.SendAsync( + new ArraySegment(new WebSocketPacket + { + Type = WebSocketPacketType.Error, + ErrorMessage = $"Unprocessable packet: {packet.Type}" + }.ToBytes()), + WebSocketMessageType.Binary, + true, + CancellationToken.None + ); + } +} \ No newline at end of file diff --git a/DysonNetwork.Pusher/Dockerfile b/DysonNetwork.Pusher/Dockerfile new file mode 100644 index 0000000..4a6845b --- /dev/null +++ b/DysonNetwork.Pusher/Dockerfile @@ -0,0 +1,23 @@ +FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base +USER $APP_UID +WORKDIR /app +EXPOSE 8080 +EXPOSE 8081 + +FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build +ARG BUILD_CONFIGURATION=Release +WORKDIR /src +COPY ["DysonNetwork.Pusher/DysonNetwork.Pusher.csproj", "DysonNetwork.Pusher/"] +RUN dotnet restore "DysonNetwork.Pusher/DysonNetwork.Pusher.csproj" +COPY . . +WORKDIR "/src/DysonNetwork.Pusher" +RUN dotnet build "./DysonNetwork.Pusher.csproj" -c $BUILD_CONFIGURATION -o /app/build + +FROM build AS publish +ARG BUILD_CONFIGURATION=Release +RUN dotnet publish "./DysonNetwork.Pusher.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false + +FROM base AS final +WORKDIR /app +COPY --from=publish /app/publish . +ENTRYPOINT ["dotnet", "DysonNetwork.Pusher.dll"] diff --git a/DysonNetwork.Pusher/DysonNetwork.Pusher.csproj b/DysonNetwork.Pusher/DysonNetwork.Pusher.csproj new file mode 100644 index 0000000..f3fcc72 --- /dev/null +++ b/DysonNetwork.Pusher/DysonNetwork.Pusher.csproj @@ -0,0 +1,21 @@ + + + + net9.0 + enable + enable + Linux + + + + + + + + + + .dockerignore + + + + diff --git a/DysonNetwork.Pusher/Program.cs b/DysonNetwork.Pusher/Program.cs new file mode 100644 index 0000000..7688653 --- /dev/null +++ b/DysonNetwork.Pusher/Program.cs @@ -0,0 +1,23 @@ +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. + +builder.Services.AddControllers(); +// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi +builder.Services.AddOpenApi(); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.MapOpenApi(); +} + +app.UseHttpsRedirection(); + +app.UseAuthorization(); + +app.MapControllers(); + +app.Run(); \ No newline at end of file diff --git a/DysonNetwork.Pusher/Properties/launchSettings.json b/DysonNetwork.Pusher/Properties/launchSettings.json new file mode 100644 index 0000000..982a366 --- /dev/null +++ b/DysonNetwork.Pusher/Properties/launchSettings.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "http://localhost:5212", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "https://localhost:7259;http://localhost:5212", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/DysonNetwork.Pusher/appsettings.json b/DysonNetwork.Pusher/appsettings.json new file mode 100644 index 0000000..82c1088 --- /dev/null +++ b/DysonNetwork.Pusher/appsettings.json @@ -0,0 +1,129 @@ +{ + "Debug": true, + "BaseUrl": "http://localhost:5071", + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*", + "ConnectionStrings": { + "App": "Host=localhost;Port=5432;Database=dyson_network;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60", + "FastRetrieve": "localhost:6379" + }, + "Authentication": { + "Schemes": { + "Bearer": { + "ValidAudiences": [ + "http://localhost:5071", + "https://localhost:7099" + ], + "ValidIssuer": "solar-network" + } + } + }, + "AuthToken": { + "PublicKeyPath": "Keys/PublicKey.pem", + "PrivateKeyPath": "Keys/PrivateKey.pem" + }, + "OidcProvider": { + "IssuerUri": "https://nt.solian.app", + "PublicKeyPath": "Keys/PublicKey.pem", + "PrivateKeyPath": "Keys/PrivateKey.pem", + "AccessTokenLifetime": "01:00:00", + "RefreshTokenLifetime": "30.00:00:00", + "AuthorizationCodeLifetime": "00:30:00", + "RequireHttpsMetadata": true + }, + "Tus": { + "StorePath": "Uploads" + }, + "Storage": { + "PreferredRemote": "minio", + "Remote": [ + { + "Id": "minio", + "Label": "Minio", + "Region": "auto", + "Bucket": "solar-network-development", + "Endpoint": "localhost:9000", + "SecretId": "littlesheep", + "SecretKey": "password", + "EnabledSigned": true, + "EnableSsl": false + }, + { + "Id": "cloudflare", + "Label": "Cloudflare R2", + "Region": "auto", + "Bucket": "solar-network", + "Endpoint": "0a70a6d1b7128888c823359d0008f4e1.r2.cloudflarestorage.com", + "SecretId": "8ff5d06c7b1639829d60bc6838a542e6", + "SecretKey": "fd58158c5201be16d1872c9209d9cf199421dae3c2f9972f94b2305976580d67", + "EnableSigned": true, + "EnableSsl": true + } + ] + }, + "Captcha": { + "Provider": "cloudflare", + "ApiKey": "0x4AAAAAABCDUdOujj4feOb_", + "ApiSecret": "0x4AAAAAABCDUWABiJQweqlB7tYq-IqIm8U" + }, + "Notifications": { + "Topic": "dev.solsynth.solian", + "Endpoint": "http://localhost:8088" + }, + "Email": { + "Server": "smtp4dev.orb.local", + "Port": 25, + "UseSsl": false, + "Username": "no-reply@mail.solsynth.dev", + "Password": "password", + "FromAddress": "no-reply@mail.solsynth.dev", + "FromName": "Alphabot", + "SubjectPrefix": "Solar Network" + }, + "RealtimeChat": { + "Endpoint": "https://solar-network-im44o8gq.livekit.cloud", + "ApiKey": "APIs6TiL8wj3A4j", + "ApiSecret": "SffxRneIwTnlHPtEf3zicmmv3LUEl7xXael4PvWZrEhE" + }, + "GeoIp": { + "DatabasePath": "./Keys/GeoLite2-City.mmdb" + }, + "Oidc": { + "Google": { + "ClientId": "961776991058-963m1qin2vtp8fv693b5fdrab5hmpl89.apps.googleusercontent.com", + "ClientSecret": "" + }, + "Apple": { + "ClientId": "dev.solsynth.solian", + "TeamId": "W7HPZ53V6B", + "KeyId": "B668YP4KBG", + "PrivateKeyPath": "./Keys/Solarpass.p8" + }, + "Microsoft": { + "ClientId": "YOUR_MICROSOFT_CLIENT_ID", + "ClientSecret": "YOUR_MICROSOFT_CLIENT_SECRET", + "DiscoveryEndpoint": "YOUR_MICROSOFT_DISCOVERY_ENDPOINT" + } + }, + "Payment": { + "Auth": { + "Afdian": "" + }, + "Subscriptions": { + "Afdian": { + "7d17aae23c9611f0b5705254001e7c00": "solian.stellar.primary", + "7dfae4743c9611f0b3a55254001e7c00": "solian.stellar.nova", + "141713ee3d6211f085b352540025c377": "solian.stellar.supernova" + } + } + }, + "KnownProxies": [ + "127.0.0.1", + "::1" + ] +} diff --git a/DysonNetwork.Shared/Data/CloudFileReferenceObject.cs b/DysonNetwork.Shared/Data/CloudFileReferenceObject.cs index ae4054e..37c0eab 100644 --- a/DysonNetwork.Shared/Data/CloudFileReferenceObject.cs +++ b/DysonNetwork.Shared/Data/CloudFileReferenceObject.cs @@ -1,3 +1,5 @@ +using Google.Protobuf.WellKnownTypes; + namespace DysonNetwork.Shared.Data; /// @@ -14,4 +16,43 @@ public class CloudFileReferenceObject : ModelBase, ICloudFile public string? Hash { get; set; } public long Size { get; set; } public bool HasCompression { get; set; } = false; + + /// + /// Converts the current object to its protobuf representation + /// + public global::DysonNetwork.Shared.Proto.CloudFileReferenceObject ToProtoValue() + { + var proto = new global::DysonNetwork.Shared.Proto.CloudFileReferenceObject + { + Id = Id, + Name = Name, + MimeType = MimeType ?? string.Empty, + Hash = Hash ?? string.Empty, + Size = Size, + HasCompression = HasCompression, + // Backward compatibility fields + ContentType = MimeType ?? string.Empty, + Url = string.Empty // This should be set by the caller if needed + }; + + // Convert file metadata + if (FileMeta != null) + { + foreach (var (key, value) in FileMeta) + { + proto.FileMeta[key] = Value.ForString(value?.ToString() ?? string.Empty); + } + } + + // Convert user metadata + if (UserMeta != null) + { + foreach (var (key, value) in UserMeta) + { + proto.UserMeta[key] = Value.ForString(value?.ToString() ?? string.Empty); + } + } + + return proto; + } } \ No newline at end of file diff --git a/DysonNetwork.Shared/DysonNetwork.Shared.csproj b/DysonNetwork.Shared/DysonNetwork.Shared.csproj index 6092e6f..364b03b 100644 --- a/DysonNetwork.Shared/DysonNetwork.Shared.csproj +++ b/DysonNetwork.Shared/DysonNetwork.Shared.csproj @@ -1,4 +1,4 @@ - + net9.0 @@ -7,12 +7,26 @@ - - - - - - + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + diff --git a/DysonNetwork.Shared/Proto/account.proto b/DysonNetwork.Shared/Proto/account.proto new file mode 100644 index 0000000..444cb49 --- /dev/null +++ b/DysonNetwork.Shared/Proto/account.proto @@ -0,0 +1,427 @@ +syntax = "proto3"; + +package proto; + +option csharp_namespace = "DysonNetwork.Shared.Proto"; + +import "google/protobuf/timestamp.proto"; +import "google/protobuf/wrappers.proto"; +import "google/protobuf/empty.proto"; +import "google/protobuf/field_mask.proto"; + +import 'file.proto'; + +// Account represents a user account in the system +message Account { + string id = 1; + string name = 2; + string nick = 3; + string language = 4; + google.protobuf.Timestamp activated_at = 5; + bool is_superuser = 6; + + AccountProfile profile = 7; + repeated AccountContact contacts = 8; + repeated AccountBadge badges = 9; + repeated AccountAuthFactor auth_factors = 10; + repeated AccountConnection connections = 11; + repeated Relationship outgoing_relationships = 12; + repeated Relationship incoming_relationships = 13; +} + +// Profile contains detailed information about a user +message AccountProfile { + string id = 1; + google.protobuf.StringValue first_name = 2; + google.protobuf.StringValue middle_name = 3; + google.protobuf.StringValue last_name = 4; + google.protobuf.StringValue bio = 5; + google.protobuf.StringValue gender = 6; + google.protobuf.StringValue pronouns = 7; + google.protobuf.StringValue time_zone = 8; + google.protobuf.StringValue location = 9; + google.protobuf.Timestamp birthday = 10; + google.protobuf.Timestamp last_seen_at = 11; + + VerificationMark verification = 12; + BadgeReferenceObject active_badge = 13; + + int32 experience = 14; + int32 level = 15; + double leveling_progress = 16; + + // Legacy fields + google.protobuf.StringValue picture_id = 17; + google.protobuf.StringValue background_id = 18; + + CloudFileReferenceObject picture = 19; + CloudFileReferenceObject background = 20; + + string account_id = 21; +} + +// AccountContact represents a contact method for an account +message AccountContact { + string id = 1; + AccountContactType type = 2; + google.protobuf.Timestamp verified_at = 3; + bool is_primary = 4; + string content = 5; + string account_id = 6; +} + +// Enum for contact types +enum AccountContactType { + ACCOUNT_CONTACT_TYPE_UNSPECIFIED = 0; + EMAIL = 1; + PHONE_NUMBER = 2; + ADDRESS = 3; +} + +// AccountAuthFactor represents an authentication factor for an account +message AccountAuthFactor { + string id = 1; + AccountAuthFactorType type = 2; + google.protobuf.StringValue secret = 3; // Omitted from JSON serialization in original + map config = 4; // Omitted from JSON serialization in original + int32 trustworthy = 5; + google.protobuf.Timestamp enabled_at = 6; + google.protobuf.Timestamp expired_at = 7; + string account_id = 8; + map created_response = 9; // For initial setup +} + +// Enum for authentication factor types +enum AccountAuthFactorType { + AUTH_FACTOR_TYPE_UNSPECIFIED = 0; + PASSWORD = 1; + EMAIL_CODE = 2; + IN_APP_CODE = 3; + TIMED_CODE = 4; + PIN_CODE = 5; +} + +// AccountBadge represents a badge associated with an account +message AccountBadge { + string id = 1; // Unique identifier for the badge + string type = 2; // Type/category of the badge + google.protobuf.StringValue label = 3; // Display name of the badge + google.protobuf.StringValue caption = 4; // Optional description of the badge + map meta = 5; // Additional metadata for the badge + google.protobuf.Timestamp activated_at = 6; // When the badge was activated + google.protobuf.Timestamp expired_at = 7; // Optional expiration time + string account_id = 8; // ID of the account this badge belongs to +} + +// AccountConnection represents a third-party connection for an account +message AccountConnection { + string id = 1; + string provider = 2; + string provided_identifier = 3; + map meta = 4; + google.protobuf.StringValue access_token = 5; // Omitted from JSON serialization + google.protobuf.StringValue refresh_token = 6; // Omitted from JSON serialization + google.protobuf.Timestamp last_used_at = 7; + string account_id = 8; +} + +// VerificationMark represents verification status +message VerificationMark { + bool verified = 1; + string method = 2; + google.protobuf.Timestamp verified_at = 3; + string verified_by = 4; +} + +// BadgeReferenceObject represents a reference to a badge with minimal information +message BadgeReferenceObject { + string id = 1; // Unique identifier for the badge + string type = 2; // Type/category of the badge + google.protobuf.StringValue label = 3; // Display name of the badge + google.protobuf.StringValue caption = 4; // Optional description of the badge + map meta = 5; // Additional metadata for the badge + google.protobuf.Timestamp activated_at = 6; // When the badge was activated + google.protobuf.Timestamp expired_at = 7; // Optional expiration time + string account_id = 8; // ID of the account this badge belongs to +} + +// Relationship represents a connection between two accounts +message Relationship { + string id = 1; + string from_account_id = 2; + string to_account_id = 3; + RelationshipType type = 4; + string note = 5; + google.protobuf.Timestamp created_at = 6; + google.protobuf.Timestamp updated_at = 7; +} + +// Enum for relationship types +enum RelationshipType { + RELATIONSHIP_TYPE_UNSPECIFIED = 0; + FRIEND = 1; + BLOCKED = 2; + PENDING_INCOMING = 3; + PENDING_OUTGOING = 4; +} + +// Leveling information +message LevelingInfo { + int32 current_level = 1; + int32 current_experience = 2; + int32 next_level_experience = 3; + int32 previous_level_experience = 4; + double level_progress = 5; + repeated int32 experience_per_level = 6; +} + +// ==================================== +// Service Definitions +// ==================================== + +// AccountService provides CRUD operations for user accounts and related entities +service AccountService { + // Account Operations + rpc GetAccount(GetAccountRequest) returns (Account) {} + rpc CreateAccount(CreateAccountRequest) returns (Account) {} + rpc UpdateAccount(UpdateAccountRequest) returns (Account) {} + rpc DeleteAccount(DeleteAccountRequest) returns (google.protobuf.Empty) {} + rpc ListAccounts(ListAccountsRequest) returns (ListAccountsResponse) {} + + // Profile Operations + rpc GetProfile(GetProfileRequest) returns (AccountProfile) {} + rpc UpdateProfile(UpdateProfileRequest) returns (AccountProfile) {} + + // Contact Operations + rpc AddContact(AddContactRequest) returns (AccountContact) {} + rpc UpdateContact(UpdateContactRequest) returns (AccountContact) {} + rpc RemoveContact(RemoveContactRequest) returns (google.protobuf.Empty) {} + rpc ListContacts(ListContactsRequest) returns (ListContactsResponse) {} + rpc VerifyContact(VerifyContactRequest) returns (AccountContact) {} + + // Badge Operations + rpc AddBadge(AddBadgeRequest) returns (AccountBadge) {} + rpc RemoveBadge(RemoveBadgeRequest) returns (google.protobuf.Empty) {} + rpc ListBadges(ListBadgesRequest) returns (ListBadgesResponse) {} + rpc SetActiveBadge(SetActiveBadgeRequest) returns (AccountProfile) {} + + // Authentication Factor Operations + rpc AddAuthFactor(AddAuthFactorRequest) returns (AccountAuthFactor) {} + rpc RemoveAuthFactor(RemoveAuthFactorRequest) returns (google.protobuf.Empty) {} + rpc ListAuthFactors(ListAuthFactorsRequest) returns (ListAuthFactorsResponse) {} + + // Connection Operations + rpc AddConnection(AddConnectionRequest) returns (AccountConnection) {} + rpc RemoveConnection(RemoveConnectionRequest) returns (google.protobuf.Empty) {} + rpc ListConnections(ListConnectionsRequest) returns (ListConnectionsResponse) {} + + // Relationship Operations + rpc CreateRelationship(CreateRelationshipRequest) returns (Relationship) {} + rpc UpdateRelationship(UpdateRelationshipRequest) returns (Relationship) {} + rpc DeleteRelationship(DeleteRelationshipRequest) returns (google.protobuf.Empty) {} + rpc ListRelationships(ListRelationshipsRequest) returns (ListRelationshipsResponse) {} +} + +// ==================================== +// Request/Response Messages +// ==================================== + +// Account Requests/Responses +message GetAccountRequest { + string id = 1; // Account ID to retrieve +} + +message CreateAccountRequest { + string name = 1; // Required: Unique username + string nick = 2; // Optional: Display name + string language = 3; // Default language + bool is_superuser = 4; // Admin flag + AccountProfile profile = 5; // Initial profile data +} + +message UpdateAccountRequest { + string id = 1; // Account ID to update + google.protobuf.StringValue name = 2; // New username if changing + google.protobuf.StringValue nick = 3; // New display name + google.protobuf.StringValue language = 4; // New language + google.protobuf.BoolValue is_superuser = 5; // Admin status +} + +message DeleteAccountRequest { + string id = 1; // Account ID to delete + bool purge = 2; // If true, permanently delete instead of soft delete +} + +message ListAccountsRequest { + int32 page_size = 1; // Number of results per page + string page_token = 2; // Token for pagination + string filter = 3; // Filter expression + string order_by = 4; // Sort order +} + +message ListAccountsResponse { + repeated Account accounts = 1; // List of accounts + string next_page_token = 2; // Token for next page + int32 total_size = 3; // Total number of accounts +} + +// Profile Requests/Responses +message GetProfileRequest { + string account_id = 1; // Account ID to get profile for +} + +message UpdateProfileRequest { + string account_id = 1; // Account ID to update profile for + AccountProfile profile = 2; // Profile data to update + google.protobuf.FieldMask update_mask = 3; // Fields to update +} + +// Contact Requests/Responses +message AddContactRequest { + string account_id = 1; // Account to add contact to + AccountContactType type = 2; // Type of contact + string content = 3; // Contact content (email, phone, etc.) + bool is_primary = 4; // If this should be the primary contact +} + +message UpdateContactRequest { + string id = 1; // Contact ID to update + string account_id = 2; // Account ID (for validation) + google.protobuf.StringValue content = 3; // New contact content + google.protobuf.BoolValue is_primary = 4; // New primary status +} + +message RemoveContactRequest { + string id = 1; // Contact ID to remove + string account_id = 2; // Account ID (for validation) +} + +message ListContactsRequest { + string account_id = 1; // Account ID to list contacts for + AccountContactType type = 2; // Optional: filter by type + bool verified_only = 3; // Only return verified contacts +} + +message ListContactsResponse { + repeated AccountContact contacts = 1; // List of contacts +} + +message VerifyContactRequest { + string id = 1; // Contact ID to verify + string account_id = 2; // Account ID (for validation) + string code = 3; // Verification code +} + +// Badge Requests/Responses +message AddBadgeRequest { + string account_id = 1; // Account to add badge to + string type = 2; // Type of badge + google.protobuf.StringValue label = 3; // Display label + google.protobuf.StringValue caption = 4; // Description + map meta = 5; // Additional metadata + google.protobuf.Timestamp expired_at = 6; // Optional expiration +} + +message RemoveBadgeRequest { + string id = 1; // Badge ID to remove + string account_id = 2; // Account ID (for validation) +} + +message ListBadgesRequest { + string account_id = 1; // Account to list badges for + string type = 2; // Optional: filter by type + bool active_only = 3; // Only return active (non-expired) badges +} + +message ListBadgesResponse { + repeated AccountBadge badges = 1; // List of badges +} + +message SetActiveBadgeRequest { + string account_id = 1; // Account to update + string badge_id = 2; // Badge ID to set as active (empty to clear) +} + +// Authentication Factor Requests/Responses +message AddAuthFactorRequest { + string account_id = 1; // Account to add factor to + AccountAuthFactorType type = 2; // Type of factor + string secret = 3; // Factor secret (hashed on server) + map config = 4; // Configuration + int32 trustworthy = 5; // Trust level + google.protobuf.Timestamp expired_at = 6; // Optional expiration +} + +message RemoveAuthFactorRequest { + string id = 1; // Factor ID to remove + string account_id = 2; // Account ID (for validation) +} + +message ListAuthFactorsRequest { + string account_id = 1; // Account to list factors for + bool active_only = 2; // Only return active (non-expired) factors +} + +message ListAuthFactorsResponse { + repeated AccountAuthFactor factors = 1; // List of auth factors +} + +// Connection Requests/Responses +message AddConnectionRequest { + string account_id = 1; // Account to add connection to + string provider = 2; // Provider name (e.g., "google", "github") + string provided_identifier = 3; // Provider's user ID + map meta = 4; // Additional metadata + google.protobuf.StringValue access_token = 5; // OAuth access token + google.protobuf.StringValue refresh_token = 6; // OAuth refresh token +} + +message RemoveConnectionRequest { + string id = 1; // Connection ID to remove + string account_id = 2; // Account ID (for validation) +} + +message ListConnectionsRequest { + string account_id = 1; // Account to list connections for + string provider = 2; // Optional: filter by provider +} + +message ListConnectionsResponse { + repeated AccountConnection connections = 1; // List of connections +} + +// Relationship Requests/Responses +message CreateRelationshipRequest { + string from_account_id = 1; // Source account ID + string to_account_id = 2; // Target account ID + RelationshipType type = 3; // Type of relationship + string note = 4; // Optional note +} + +message UpdateRelationshipRequest { + string id = 1; // Relationship ID to update + string account_id = 2; // Account ID (for validation) + RelationshipType type = 3; // New relationship type + google.protobuf.StringValue note = 4; // New note +} + +message DeleteRelationshipRequest { + string id = 1; // Relationship ID to delete + string account_id = 2; // Account ID (for validation) +} + +message ListRelationshipsRequest { + string account_id = 1; // Account to list relationships for + RelationshipType type = 2; // Optional: filter by type + bool incoming = 3; // If true, list incoming relationships + bool outgoing = 4; // If true, list outgoing relationships + int32 page_size = 5; // Number of results per page + string page_token = 6; // Token for pagination +} + +message ListRelationshipsResponse { + repeated Relationship relationships = 1; // List of relationships + string next_page_token = 2; // Token for next page + int32 total_size = 3; // Total number of relationships +} + diff --git a/DysonNetwork.Shared/Proto/file.proto b/DysonNetwork.Shared/Proto/file.proto new file mode 100644 index 0000000..593e2b7 --- /dev/null +++ b/DysonNetwork.Shared/Proto/file.proto @@ -0,0 +1,87 @@ +syntax = "proto3"; + +package proto; + +option csharp_namespace = "DysonNetwork.Shared.Proto"; + +import "google/protobuf/timestamp.proto"; +import "google/protobuf/struct.proto"; +import "google/protobuf/empty.proto"; +import "google/protobuf/field_mask.proto"; + +// CloudFileReferenceObject represents a reference to a file stored in cloud storage. +// It contains metadata about the file that won't change, helping to reduce database load. +message CloudFileReferenceObject { + // Unique identifier for the file + string id = 1; + + // Original name of the file + string name = 2; + + // File metadata (e.g., dimensions, duration, etc.) + map file_meta = 3; + + // User-defined metadata + map user_meta = 4; + + // MIME type of the file + string mime_type = 5; + + // File content hash (e.g., MD5, SHA-256) + string hash = 6; + + // File size in bytes + int64 size = 7; + + // Indicates if the file is stored with compression + bool has_compression = 8; + + // URL to access the file (kept for backward compatibility) + string url = 9; + + // Content type of the file (kept for backward compatibility) + string content_type = 10; + + // When the file was uploaded (kept for backward compatibility) + google.protobuf.Timestamp uploaded_at = 11; + + // Additional metadata (kept for backward compatibility) + map metadata = 12; +} + +// Service for file operations +service FileService { + // Get file reference by ID + rpc GetFile(GetFileRequest) returns (CloudFileReferenceObject) {} + + // Create a new file reference + rpc CreateFile(CreateFileRequest) returns (CloudFileReferenceObject) {} + + // Update an existing file reference + rpc UpdateFile(UpdateFileRequest) returns (CloudFileReferenceObject) {} + + // Delete a file reference + rpc DeleteFile(DeleteFileRequest) returns (google.protobuf.Empty) {} +} + +// Request message for GetFile +message GetFileRequest { + string id = 1; +} + +// Request message for CreateFile +message CreateFileRequest { + CloudFileReferenceObject file = 1; +} + +// Request message for UpdateFile +message UpdateFileRequest { + CloudFileReferenceObject file = 1; + google.protobuf.FieldMask update_mask = 2; +} + +// Request message for DeleteFile +message DeleteFileRequest { + string id = 1; + bool hard_delete = 2; +} diff --git a/DysonNetwork.Sphere/DysonNetwork.Sphere.csproj b/DysonNetwork.Sphere/DysonNetwork.Sphere.csproj index 72aa5d7..7532645 100644 --- a/DysonNetwork.Sphere/DysonNetwork.Sphere.csproj +++ b/DysonNetwork.Sphere/DysonNetwork.Sphere.csproj @@ -23,6 +23,7 @@ + diff --git a/DysonNetwork.sln b/DysonNetwork.sln index 7ee589d..4b2f2be 100644 --- a/DysonNetwork.sln +++ b/DysonNetwork.sln @@ -11,6 +11,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DysonNetwork.Pass", "DysonN EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DysonNetwork.Shared", "DysonNetwork.Shared\DysonNetwork.Shared.csproj", "{DB46D1A6-79B4-43FC-A9A9-115CDA26947A}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DysonNetwork.Pusher", "DysonNetwork.Pusher\DysonNetwork.Pusher.csproj", "{D5DAFB0D-487E-48EF-BA2F-C581C846F63B}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -29,5 +31,9 @@ Global {DB46D1A6-79B4-43FC-A9A9-115CDA26947A}.Debug|Any CPU.Build.0 = Debug|Any CPU {DB46D1A6-79B4-43FC-A9A9-115CDA26947A}.Release|Any CPU.ActiveCfg = Release|Any CPU {DB46D1A6-79B4-43FC-A9A9-115CDA26947A}.Release|Any CPU.Build.0 = Release|Any CPU + {D5DAFB0D-487E-48EF-BA2F-C581C846F63B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D5DAFB0D-487E-48EF-BA2F-C581C846F63B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D5DAFB0D-487E-48EF-BA2F-C581C846F63B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D5DAFB0D-487E-48EF-BA2F-C581C846F63B}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal From e1b47bc7d158717a87a412172894ad50d2a52ee5 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Sat, 12 Jul 2025 22:15:18 +0800 Subject: [PATCH 05/42] :sparkles: Pusher service basis --- DysonNetwork.Pass/Account/Account.cs | 76 ++++++ .../Account/AccountServiceGrpc.cs | 193 +++++++------ DysonNetwork.Pass/Account/Badge.cs | 38 +++ DysonNetwork.Pass/Account/VerificationMark.cs | 27 +- DysonNetwork.Pass/Auth/AuthServiceGrpc.cs | 27 ++ DysonNetwork.Pass/Auth/Session.cs | 32 +++ DysonNetwork.Pusher/AppDatabase.cs | 178 ++++++++++++ .../Connection/IWebSocketPacketHandler.cs | 10 +- .../Connection/WebSocketController.cs | 13 +- .../Connection/WebSocketPacket.cs | 6 +- .../Connection/WebSocketService.cs | 25 +- .../DysonNetwork.Pusher.csproj | 15 + DysonNetwork.Pusher/Email/EmailService.cs | 86 ++++++ .../Notification/Notification.cs | 24 ++ .../Notification/PushService.cs | 258 ++++++++++++++++++ .../Notification/PushSubscription.cs | 25 ++ .../DysonNetwork.Shared.csproj | 1 + DysonNetwork.Shared/Proto/GrpcTypeHelper.cs | 89 ++++++ DysonNetwork.Shared/Proto/account.proto | 26 +- DysonNetwork.Shared/Proto/auth.proto | 68 +++++ .../DysonNetwork.Sphere.csproj | 2 +- DysonNetwork.sln.DotSettings.user | 2 + 22 files changed, 1117 insertions(+), 104 deletions(-) create mode 100644 DysonNetwork.Pass/Auth/AuthServiceGrpc.cs create mode 100644 DysonNetwork.Pusher/AppDatabase.cs create mode 100644 DysonNetwork.Pusher/Email/EmailService.cs create mode 100644 DysonNetwork.Pusher/Notification/Notification.cs create mode 100644 DysonNetwork.Pusher/Notification/PushService.cs create mode 100644 DysonNetwork.Pusher/Notification/PushSubscription.cs create mode 100644 DysonNetwork.Shared/Proto/GrpcTypeHelper.cs create mode 100644 DysonNetwork.Shared/Proto/auth.proto diff --git a/DysonNetwork.Pass/Account/Account.cs b/DysonNetwork.Pass/Account/Account.cs index a4183aa..3078c7c 100644 --- a/DysonNetwork.Pass/Account/Account.cs +++ b/DysonNetwork.Pass/Account/Account.cs @@ -4,6 +4,7 @@ using System.Text.Json.Serialization; using DysonNetwork.Shared.Data; using Microsoft.EntityFrameworkCore; using NodaTime; +using NodaTime.Serialization.Protobuf; using OtpNet; namespace DysonNetwork.Pass.Account; @@ -29,6 +30,30 @@ public class Account : ModelBase [JsonIgnore] public ICollection OutgoingRelationships { get; set; } = new List(); [JsonIgnore] public ICollection IncomingRelationships { get; set; } = new List(); + + public Shared.Proto.Account ToProtoValue() + { + var proto = new Shared.Proto.Account + { + Id = Id.ToString(), + Name = Name, + Nick = Nick, + Language = Language, + ActivatedAt = ActivatedAt?.ToTimestamp(), + IsSuperuser = IsSuperuser, + Profile = Profile.ToProtoValue() + }; + + // Add contacts + foreach (var contact in Contacts) + proto.Contacts.Add(contact.ToProtoValue()); + + // Add badges + foreach (var badge in Badges) + proto.Badges.Add(badge.ToProtoValue()); + + return proto; + } } public abstract class Leveling @@ -88,6 +113,36 @@ public class AccountProfile : ModelBase public Guid AccountId { get; set; } [JsonIgnore] public Account Account { get; set; } = null!; + + public Shared.Proto.AccountProfile ToProtoValue() + { + var proto = new Shared.Proto.AccountProfile + { + Id = Id.ToString(), + FirstName = FirstName ?? string.Empty, + MiddleName = MiddleName ?? string.Empty, + LastName = LastName ?? string.Empty, + Bio = Bio ?? string.Empty, + Gender = Gender ?? string.Empty, + Pronouns = Pronouns ?? string.Empty, + TimeZone = TimeZone ?? string.Empty, + Location = Location ?? string.Empty, + Birthday = Birthday?.ToTimestamp(), + LastSeenAt = LastSeenAt?.ToTimestamp(), + Experience = Experience, + Level = Level, + LevelingProgress = LevelingProgress, + PictureId = PictureId ?? string.Empty, + BackgroundId = BackgroundId ?? string.Empty, + Picture = Picture?.ToProtoValue(), + Background = Background?.ToProtoValue(), + AccountId = AccountId.ToString(), + Verification = Verification?.ToProtoValue(), + ActiveBadge = ActiveBadge?.ToProtoValue() + }; + + return proto; + } } public class AccountContact : ModelBase @@ -100,6 +155,27 @@ public class AccountContact : ModelBase public Guid AccountId { get; set; } [JsonIgnore] public Account Account { get; set; } = null!; + + public Shared.Proto.AccountContact ToProtoValue() + { + var proto = new Shared.Proto.AccountContact + { + Id = Id.ToString(), + Type = Type switch + { + AccountContactType.Email => Shared.Proto.AccountContactType.Email, + AccountContactType.PhoneNumber => Shared.Proto.AccountContactType.PhoneNumber, + AccountContactType.Address => Shared.Proto.AccountContactType.Address, + _ => Shared.Proto.AccountContactType.Unspecified + }, + Content = Content, + IsPrimary = IsPrimary, + VerifiedAt = VerifiedAt?.ToTimestamp(), + AccountId = AccountId.ToString() + }; + + return proto; + } } public enum AccountContactType diff --git a/DysonNetwork.Pass/Account/AccountServiceGrpc.cs b/DysonNetwork.Pass/Account/AccountServiceGrpc.cs index 9ccbac7..7450de1 100644 --- a/DysonNetwork.Pass/Account/AccountServiceGrpc.cs +++ b/DysonNetwork.Pass/Account/AccountServiceGrpc.cs @@ -19,72 +19,7 @@ public class AccountServiceGrpc( private readonly ILogger _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - - // Helper methods for conversion between protobuf and domain models - private static Shared.Proto.Account ToProtoAccount(Account account) => new() - { - Id = account.Id.ToString(), - Name = account.Name, - Nick = account.Nick, - Language = account.Language, - ActivatedAt = account.ActivatedAt?.ToTimestamp(), - IsSuperuser = account.IsSuperuser, - Profile = ToProtoProfile(account.Profile) - // Note: Collections are not included by default to avoid large payloads - // They should be loaded on demand via specific methods - }; - - private static Shared.Proto.AccountProfile ToProtoProfile(AccountProfile profile) => new() - { - Id = profile.Id.ToString(), - FirstName = profile.FirstName, - MiddleName = profile.MiddleName, - LastName = profile.LastName, - Bio = profile.Bio, - Gender = profile.Gender, - Pronouns = profile.Pronouns, - TimeZone = profile.TimeZone, - Location = profile.Location, - Birthday = profile.Birthday?.ToTimestamp(), - LastSeenAt = profile.LastSeenAt?.ToTimestamp(), - Experience = profile.Experience, - Level = profile.Level, - LevelingProgress = profile.LevelingProgress, - AccountId = profile.AccountId.ToString(), - PictureId = profile.PictureId, - BackgroundId = profile.BackgroundId, - Picture = profile.Picture?.ToProtoValue(), - Background = profile.Background?.ToProtoValue() - }; - - private static Shared.Proto.AccountContact ToProtoContact(AccountContact contact) => new() - { - Id = contact.Id.ToString(), - Type = contact.Type switch - { - AccountContactType.Address => Shared.Proto.AccountContactType.Address, - AccountContactType.PhoneNumber => Shared.Proto.AccountContactType.PhoneNumber, - AccountContactType.Email => Shared.Proto.AccountContactType.Email, - _ => Shared.Proto.AccountContactType.Unspecified - }, - VerifiedAt = contact.VerifiedAt?.ToTimestamp(), - IsPrimary = contact.IsPrimary, - Content = contact.Content, - AccountId = contact.AccountId.ToString() - }; - - private static Shared.Proto.AccountBadge ToProtoBadge(AccountBadge badge) => new() - { - Id = badge.Id.ToString(), - Type = badge.Type, - Label = badge.Label, - Caption = badge.Caption, - ActivatedAt = badge.ActivatedAt?.ToTimestamp(), - ExpiredAt = badge.ExpiredAt?.ToTimestamp(), - AccountId = badge.AccountId.ToString() - }; - -// Implementation of gRPC service methods + public override async Task GetAccount(GetAccountRequest request, ServerCallContext context) { if (!Guid.TryParse(request.Id, out var accountId)) @@ -98,7 +33,7 @@ public class AccountServiceGrpc( if (account == null) throw new RpcException(new Grpc.Core.Status(StatusCode.NotFound, $"Account {request.Id} not found")); - return ToProtoAccount(account); + return account.ToProtoValue(); } public override async Task CreateAccount(CreateAccountRequest request, @@ -125,7 +60,7 @@ public class AccountServiceGrpc( await _db.SaveChangesAsync(); _logger.LogInformation("Created new account with ID {AccountId}", account.Id); - return ToProtoAccount(account); + return account.ToProtoValue(); } public override async Task UpdateAccount(UpdateAccountRequest request, @@ -145,7 +80,7 @@ public class AccountServiceGrpc( if (request.IsSuperuser != null) account.IsSuperuser = request.IsSuperuser.Value; await _db.SaveChangesAsync(); - return ToProtoAccount(account); + return account.ToProtoValue(); } public override async Task DeleteAccount(DeleteAccountRequest request, ServerCallContext context) @@ -202,7 +137,7 @@ public class AccountServiceGrpc( : "" }; - response.Accounts.AddRange(accounts.Select(ToProtoAccount)); + response.Accounts.AddRange(accounts.Select(x => x.ToProtoValue())); return response; } @@ -223,7 +158,7 @@ public class AccountServiceGrpc( throw new RpcException(new Grpc.Core.Status(StatusCode.NotFound, $"Profile for account {request.AccountId} not found")); - return ToProtoProfile(profile); + return profile.ToProtoValue(); } public override async Task UpdateProfile(UpdateProfileRequest request, @@ -249,7 +184,7 @@ public class AccountServiceGrpc( // Update other fields similarly... await _db.SaveChangesAsync(); - return ToProtoProfile(profile); + return profile.ToProtoValue(); } // Contact operations @@ -271,10 +206,65 @@ public class AccountServiceGrpc( _db.AccountContacts.Add(contact); await _db.SaveChangesAsync(); - return ToProtoContact(contact); + return contact.ToProtoValue(); } -// Implement other contact operations... + public override async Task RemoveContact(RemoveContactRequest request, ServerCallContext context) + { + if (!Guid.TryParse(request.AccountId, out var accountId)) + throw new RpcException(new Grpc.Core.Status(StatusCode.InvalidArgument, "Invalid account ID format")); + + if (!Guid.TryParse(request.Id, out var contactId)) + throw new RpcException(new Grpc.Core.Status(StatusCode.InvalidArgument, "Invalid contact ID format")); + + var contact = await _db.AccountContacts.FirstOrDefaultAsync(c => c.Id == contactId && c.AccountId == accountId); + if (contact == null) + throw new RpcException(new Grpc.Core.Status(StatusCode.NotFound, "Contact not found.")); + + _db.AccountContacts.Remove(contact); + await _db.SaveChangesAsync(); + + return new Empty(); + } + + public override async Task ListContacts(ListContactsRequest request, ServerCallContext context) + { + if (!Guid.TryParse(request.AccountId, out var accountId)) + throw new RpcException(new Grpc.Core.Status(StatusCode.InvalidArgument, "Invalid account ID format")); + + var query = _db.AccountContacts.AsNoTracking().Where(c => c.AccountId == accountId); + + if (request.VerifiedOnly) + query = query.Where(c => c.VerifiedAt != null); + + var contacts = await query.ToListAsync(); + + var response = new ListContactsResponse(); + response.Contacts.AddRange(contacts.Select(c => c.ToProtoValue())); + + return response; + } + + public override async Task VerifyContact(VerifyContactRequest request, ServerCallContext context) + { + // This is a placeholder implementation. In a real-world scenario, you would + // have a more robust verification mechanism (e.g., sending a code to the + // user's email or phone). + if (!Guid.TryParse(request.AccountId, out var accountId)) + throw new RpcException(new Grpc.Core.Status(StatusCode.InvalidArgument, "Invalid account ID format")); + + if (!Guid.TryParse(request.Id, out var contactId)) + throw new RpcException(new Grpc.Core.Status(StatusCode.InvalidArgument, "Invalid contact ID format")); + + var contact = await _db.AccountContacts.FirstOrDefaultAsync(c => c.Id == contactId && c.AccountId == accountId); + if (contact == null) + throw new RpcException(new Grpc.Core.Status(StatusCode.NotFound, "Contact not found.")); + + contact.VerifiedAt = _clock.GetCurrentInstant(); + await _db.SaveChangesAsync(); + + return contact.ToProtoValue(); + } // Badge operations public override async Task AddBadge(AddBadgeRequest request, ServerCallContext context) @@ -296,8 +286,59 @@ public class AccountServiceGrpc( _db.Badges.Add(badge); await _db.SaveChangesAsync(); - return ToProtoBadge(badge); + return badge.ToProtoValue(); } -// Implement other badge operations... + public override async Task RemoveBadge(RemoveBadgeRequest request, ServerCallContext context) + { + if (!Guid.TryParse(request.AccountId, out var accountId)) + throw new RpcException(new Grpc.Core.Status(StatusCode.InvalidArgument, "Invalid account ID format")); + + if (!Guid.TryParse(request.Id, out var badgeId)) + throw new RpcException(new Grpc.Core.Status(StatusCode.InvalidArgument, "Invalid badge ID format")); + + var badge = await _db.Badges.FirstOrDefaultAsync(b => b.Id == badgeId && b.AccountId == accountId); + if (badge == null) + throw new RpcException(new Grpc.Core.Status(StatusCode.NotFound, "Badge not found.")); + + _db.Badges.Remove(badge); + await _db.SaveChangesAsync(); + + return new Empty(); + } + + public override async Task ListBadges(ListBadgesRequest request, ServerCallContext context) + { + if (!Guid.TryParse(request.AccountId, out var accountId)) + throw new RpcException(new Grpc.Core.Status(StatusCode.InvalidArgument, "Invalid account ID format")); + + var query = _db.Badges.AsNoTracking().Where(b => b.AccountId == accountId); + + if (request.ActiveOnly) + query = query.Where(b => b.ExpiredAt == null || b.ExpiredAt > _clock.GetCurrentInstant()); + + var badges = await query.ToListAsync(); + + var response = new ListBadgesResponse(); + response.Badges.AddRange(badges.Select(b => b.ToProtoValue())); + + return response; + } + + public override async Task SetActiveBadge(SetActiveBadgeRequest request, ServerCallContext context) + { + if (!Guid.TryParse(request.AccountId, out var accountId)) + throw new RpcException(new Grpc.Core.Status(StatusCode.InvalidArgument, "Invalid account ID format")); + + var profile = await _db.AccountProfiles.FirstOrDefaultAsync(p => p.AccountId == accountId); + if (profile == null) + throw new RpcException(new Grpc.Core.Status(StatusCode.NotFound, "Profile not found.")); + + if (!string.IsNullOrEmpty(request.BadgeId) && !Guid.TryParse(request.BadgeId, out var badgeId)) + throw new RpcException(new Grpc.Core.Status(StatusCode.InvalidArgument, "Invalid badge ID format")); + + await _db.SaveChangesAsync(); + + return profile.ToProtoValue(); + } } \ No newline at end of file diff --git a/DysonNetwork.Pass/Account/Badge.cs b/DysonNetwork.Pass/Account/Badge.cs index b8cb8b1..8f61dcf 100644 --- a/DysonNetwork.Pass/Account/Badge.cs +++ b/DysonNetwork.Pass/Account/Badge.cs @@ -2,7 +2,10 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using System.Text.Json.Serialization; using DysonNetwork.Shared.Data; +using DysonNetwork.Shared.Proto; +using Google.Protobuf.WellKnownTypes; using NodaTime; +using NodaTime.Serialization.Protobuf; namespace DysonNetwork.Pass.Account; @@ -33,6 +36,23 @@ public class AccountBadge : ModelBase AccountId = AccountId }; } + + public Shared.Proto.AccountBadge ToProtoValue() + { + var proto = new Shared.Proto.AccountBadge + { + Id = Id.ToString(), + Type = Type, + Label = Label ?? string.Empty, + Caption = Caption ?? string.Empty, + ActivatedAt = ActivatedAt?.ToTimestamp(), + ExpiredAt = ExpiredAt?.ToTimestamp(), + AccountId = AccountId.ToString(), + }; + proto.Meta.Add(GrpcTypeHelper.ConvertToValueMap(Meta)); + + return proto; + } } public class BadgeReferenceObject : ModelBase @@ -45,4 +65,22 @@ public class BadgeReferenceObject : ModelBase public Instant? ActivatedAt { get; set; } public Instant? ExpiredAt { get; set; } public Guid AccountId { get; set; } + + public Shared.Proto.BadgeReferenceObject ToProtoValue() + { + var proto = new Shared.Proto.BadgeReferenceObject + { + Id = Id.ToString(), + Type = Type, + Label = Label ?? string.Empty, + Caption = Caption ?? string.Empty, + ActivatedAt = ActivatedAt?.ToTimestamp(), + ExpiredAt = ExpiredAt?.ToTimestamp(), + AccountId = AccountId.ToString() + }; + if (Meta is not null) + proto.Meta.Add(GrpcTypeHelper.ConvertToValueMap(Meta!)); + + return proto; + } } \ No newline at end of file diff --git a/DysonNetwork.Pass/Account/VerificationMark.cs b/DysonNetwork.Pass/Account/VerificationMark.cs index fc6a419..dbd4159 100644 --- a/DysonNetwork.Pass/Account/VerificationMark.cs +++ b/DysonNetwork.Pass/Account/VerificationMark.cs @@ -13,6 +13,29 @@ public class VerificationMark [MaxLength(1024)] public string? Title { get; set; } [MaxLength(8192)] public string? Description { get; set; } [MaxLength(1024)] public string? VerifiedBy { get; set; } + + public Shared.Proto.VerificationMark ToProtoValue() + { + var proto = new Shared.Proto.VerificationMark + { + Type = Type switch + { + VerificationMarkType.Official => Shared.Proto.VerificationMarkType.Official, + VerificationMarkType.Individual => Shared.Proto.VerificationMarkType.Individual, + VerificationMarkType.Organization => Shared.Proto.VerificationMarkType.Organization, + VerificationMarkType.Government => Shared.Proto.VerificationMarkType.Government, + VerificationMarkType.Creator => Shared.Proto.VerificationMarkType.Creator, + VerificationMarkType.Developer => Shared.Proto.VerificationMarkType.Developer, + VerificationMarkType.Parody => Shared.Proto.VerificationMarkType.Parody, + _ => Shared.Proto.VerificationMarkType.Unspecified + }, + Title = Title ?? string.Empty, + Description = Description ?? string.Empty, + VerifiedBy = VerifiedBy ?? string.Empty + }; + + return proto; + } } public enum VerificationMarkType @@ -21,5 +44,7 @@ public enum VerificationMarkType Individual, Organization, Government, - Creator + Creator, + Developer, + Parody } \ No newline at end of file diff --git a/DysonNetwork.Pass/Auth/AuthServiceGrpc.cs b/DysonNetwork.Pass/Auth/AuthServiceGrpc.cs new file mode 100644 index 0000000..208bceb --- /dev/null +++ b/DysonNetwork.Pass/Auth/AuthServiceGrpc.cs @@ -0,0 +1,27 @@ +using DysonNetwork.Shared.Proto; +using Grpc.Core; +using Microsoft.EntityFrameworkCore; + +namespace DysonNetwork.Pass.Auth; + +public class AuthServiceGrpc(AuthService authService, AppDatabase db) : Shared.Proto.AuthService +{ + public async Task Authenticate(AuthenticateRequest request, ServerCallContext context) + { + if (!authService.ValidateToken(request.Token, out var sessionId)) + { + throw new RpcException(new Status(StatusCode.Unauthenticated, "Invalid token.")); + } + + var session = await db.AuthSessions + .AsNoTracking() + .FirstOrDefaultAsync(s => s.Id == sessionId); + + if (session == null) + { + throw new RpcException(new Status(StatusCode.NotFound, "Session not found.")); + } + + return session.ToProtoValue(); + } +} diff --git a/DysonNetwork.Pass/Auth/Session.cs b/DysonNetwork.Pass/Auth/Session.cs index bcd0ced..d5f935d 100644 --- a/DysonNetwork.Pass/Auth/Session.cs +++ b/DysonNetwork.Pass/Auth/Session.cs @@ -4,6 +4,7 @@ using System.Text.Json.Serialization; using DysonNetwork.Pass; using DysonNetwork.Shared.Data; using NodaTime; +using NodaTime.Serialization.Protobuf; using Point = NetTopologySuite.Geometries.Point; namespace DysonNetwork.Pass.Auth; @@ -21,6 +22,18 @@ public class AuthSession : ModelBase public AuthChallenge Challenge { get; set; } = null!; public Guid? AppId { get; set; } // public CustomApp? App { get; set; } + + public Shared.Proto.AuthSession ToProtoValue() => new() + { + Id = Id.ToString(), + Label = Label, + LastGrantedAt = LastGrantedAt?.ToTimestamp(), + ExpiredAt = ExpiredAt?.ToTimestamp(), + AccountId = AccountId.ToString(), + ChallengeId = ChallengeId.ToString(), + Challenge = Challenge.ToProtoValue(), + AppId = AppId?.ToString() + }; } public enum ChallengeType @@ -67,4 +80,23 @@ public class AuthChallenge : ModelBase if (StepRemain == 0 && BlacklistFactors.Count == 0) StepRemain = StepTotal; return this; } + + public Shared.Proto.AuthChallenge ToProtoValue() => new() + { + Id = Id.ToString(), + ExpiredAt = ExpiredAt?.ToTimestamp(), + StepRemain = StepRemain, + StepTotal = StepTotal, + FailedAttempts = FailedAttempts, + Platform = (Shared.Proto.ChallengePlatform)Platform, + Type = (Shared.Proto.ChallengeType)Type, + BlacklistFactors = { BlacklistFactors.Select(x => x.ToString()) }, + Audiences = { Audiences }, + Scopes = { Scopes }, + IpAddress = IpAddress, + UserAgent = UserAgent, + DeviceId = DeviceId, + Nonce = Nonce, + AccountId = AccountId.ToString() + }; } \ No newline at end of file diff --git a/DysonNetwork.Pusher/AppDatabase.cs b/DysonNetwork.Pusher/AppDatabase.cs new file mode 100644 index 0000000..7f247d5 --- /dev/null +++ b/DysonNetwork.Pusher/AppDatabase.cs @@ -0,0 +1,178 @@ +using System.Linq.Expressions; +using System.Reflection; +using DysonNetwork.Pusher.Notification; +using DysonNetwork.Shared.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Design; +using Microsoft.EntityFrameworkCore.Query; +using NodaTime; +using Quartz; + +namespace DysonNetwork.Pusher; + +public class AppDatabase( + DbContextOptions options, + IConfiguration configuration +) : DbContext(options) +{ + public DbSet Notifications { get; set; } = null!; + public DbSet PushSubscriptions { get; set; } + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + optionsBuilder.UseNpgsql( + configuration.GetConnectionString("App"), + opt => opt + .ConfigureDataSource(optSource => optSource.EnableDynamicJson()) + .UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery) + .UseNodaTime() + ).UseSnakeCaseNamingConvention(); + + base.OnConfiguring(optionsBuilder); + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + // Automatically apply soft-delete filter to all entities inheriting BaseModel + foreach (var entityType in modelBuilder.Model.GetEntityTypes()) + { + if (!typeof(ModelBase).IsAssignableFrom(entityType.ClrType)) continue; + var method = typeof(AppDatabase) + .GetMethod(nameof(SetSoftDeleteFilter), + BindingFlags.NonPublic | BindingFlags.Static)! + .MakeGenericMethod(entityType.ClrType); + + method.Invoke(null, [modelBuilder]); + } + } + + private static void SetSoftDeleteFilter(ModelBuilder modelBuilder) + where TEntity : ModelBase + { + modelBuilder.Entity().HasQueryFilter(e => e.DeletedAt == null); + } + + public override async Task SaveChangesAsync(CancellationToken cancellationToken = default) + { + var now = SystemClock.Instance.GetCurrentInstant(); + + foreach (var entry in ChangeTracker.Entries()) + { + switch (entry.State) + { + case EntityState.Added: + entry.Entity.CreatedAt = now; + entry.Entity.UpdatedAt = now; + break; + case EntityState.Modified: + entry.Entity.UpdatedAt = now; + break; + case EntityState.Deleted: + entry.State = EntityState.Modified; + entry.Entity.DeletedAt = now; + break; + case EntityState.Detached: + case EntityState.Unchanged: + default: + break; + } + } + + return await base.SaveChangesAsync(cancellationToken); + } +} + +public class AppDatabaseRecyclingJob(AppDatabase db, ILogger logger) : IJob +{ + public async Task Execute(IJobExecutionContext context) + { + var now = SystemClock.Instance.GetCurrentInstant(); + + logger.LogInformation("Deleting soft-deleted records..."); + + var threshold = now - Duration.FromDays(7); + + var entityTypes = db.Model.GetEntityTypes() + .Where(t => typeof(ModelBase).IsAssignableFrom(t.ClrType) && t.ClrType != typeof(ModelBase)) + .Select(t => t.ClrType); + + foreach (var entityType in entityTypes) + { + var set = (IQueryable)db.GetType().GetMethod(nameof(DbContext.Set), Type.EmptyTypes)! + .MakeGenericMethod(entityType).Invoke(db, null)!; + var parameter = Expression.Parameter(entityType, "e"); + var property = Expression.Property(parameter, nameof(ModelBase.DeletedAt)); + var condition = Expression.LessThan(property, Expression.Constant(threshold, typeof(Instant?))); + var notNull = Expression.NotEqual(property, Expression.Constant(null, typeof(Instant?))); + var finalCondition = Expression.AndAlso(notNull, condition); + var lambda = Expression.Lambda(finalCondition, parameter); + + var queryable = set.Provider.CreateQuery( + Expression.Call( + typeof(Queryable), + "Where", + [entityType], + set.Expression, + Expression.Quote(lambda) + ) + ); + + var toListAsync = typeof(EntityFrameworkQueryableExtensions) + .GetMethod(nameof(EntityFrameworkQueryableExtensions.ToListAsync))! + .MakeGenericMethod(entityType); + + var items = await (dynamic)toListAsync.Invoke(null, [queryable, CancellationToken.None])!; + db.RemoveRange(items); + } + + await db.SaveChangesAsync(); + } +} + +public class AppDatabaseFactory : IDesignTimeDbContextFactory +{ + public AppDatabase CreateDbContext(string[] args) + { + var configuration = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json") + .Build(); + + var optionsBuilder = new DbContextOptionsBuilder(); + return new AppDatabase(optionsBuilder.Options, configuration); + } +} + +public static class OptionalQueryExtensions +{ + public static IQueryable If( + this IQueryable source, + bool condition, + Func, IQueryable> transform + ) + { + return condition ? transform(source) : source; + } + + public static IQueryable If( + this IIncludableQueryable source, + bool condition, + Func, IQueryable> transform + ) + where T : class + { + return condition ? transform(source) : source; + } + + public static IQueryable If( + this IIncludableQueryable> source, + bool condition, + Func>, IQueryable> transform + ) + where T : class + { + return condition ? transform(source) : source; + } +} \ No newline at end of file diff --git a/DysonNetwork.Pusher/Connection/IWebSocketPacketHandler.cs b/DysonNetwork.Pusher/Connection/IWebSocketPacketHandler.cs index 0d5c591..969285b 100644 --- a/DysonNetwork.Pusher/Connection/IWebSocketPacketHandler.cs +++ b/DysonNetwork.Pusher/Connection/IWebSocketPacketHandler.cs @@ -1,9 +1,17 @@ using System.Net.WebSockets; +using DysonNetwork.Shared.Proto; namespace DysonNetwork.Pusher.Connection; public interface IWebSocketPacketHandler { string PacketType { get; } - Task HandleAsync(Account currentUser, string deviceId, WebSocketPacket packet, WebSocket socket, WebSocketService srv); + + Task HandleAsync( + Account currentUser, + string deviceId, + WebSocketPacket packet, + WebSocket socket, + WebSocketService srv + ); } \ No newline at end of file diff --git a/DysonNetwork.Pusher/Connection/WebSocketController.cs b/DysonNetwork.Pusher/Connection/WebSocketController.cs index 8f81f04..a7135d8 100644 --- a/DysonNetwork.Pusher/Connection/WebSocketController.cs +++ b/DysonNetwork.Pusher/Connection/WebSocketController.cs @@ -1,8 +1,7 @@ -using System.Collections.Concurrent; using System.Net.WebSockets; +using DysonNetwork.Shared.Proto; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore.Metadata.Internal; using Swashbuckle.AspNetCore.Annotations; namespace DysonNetwork.Pusher.Connection; @@ -18,15 +17,15 @@ public class WebSocketController(WebSocketService ws, ILogger { HttpContext.Items.TryGetValue("CurrentUser", out var currentUserValue); HttpContext.Items.TryGetValue("CurrentSession", out var currentSessionValue); - if (currentUserValue is not Account.Account currentUser || - currentSessionValue is not Auth.Session currentSession) + if (currentUserValue is not Account currentUser || + currentSessionValue is not AuthSession currentSession) { HttpContext.Response.StatusCode = StatusCodes.Status401Unauthorized; return; } - var accountId = currentUser.Id; - var deviceId = currentSession.Challenge.DeviceId; + var accountId = currentUser.Id!; + var deviceId = currentSession.Challenge.DeviceId!; if (string.IsNullOrEmpty(deviceId)) { @@ -69,7 +68,7 @@ public class WebSocketController(WebSocketService ws, ILogger private async Task _ConnectionEventLoop( string deviceId, - Account.Account currentUser, + Account currentUser, WebSocket webSocket, CancellationToken cancellationToken ) diff --git a/DysonNetwork.Pusher/Connection/WebSocketPacket.cs b/DysonNetwork.Pusher/Connection/WebSocketPacket.cs index 745a961..f20b961 100644 --- a/DysonNetwork.Pusher/Connection/WebSocketPacket.cs +++ b/DysonNetwork.Pusher/Connection/WebSocketPacket.cs @@ -2,7 +2,9 @@ using System.Text.Json; using NodaTime; using NodaTime.Serialization.SystemTextJson; -public class WebSocketPacketType +namespace DysonNetwork.Pusher.Connection; + +public abstract class WebSocketPacketType { public const string Error = "error"; public const string MessageNew = "messages.new"; @@ -31,7 +33,7 @@ public class WebSocketPacket DictionaryKeyPolicy = JsonNamingPolicy.SnakeCaseLower, }; return JsonSerializer.Deserialize(json, jsonOpts) ?? - throw new JsonException("Failed to deserialize WebSocketPacket"); + throw new JsonException("Failed to deserialize WebSocketPacket"); } /// diff --git a/DysonNetwork.Pusher/Connection/WebSocketService.cs b/DysonNetwork.Pusher/Connection/WebSocketService.cs index 948db1d..9022a3a 100644 --- a/DysonNetwork.Pusher/Connection/WebSocketService.cs +++ b/DysonNetwork.Pusher/Connection/WebSocketService.cs @@ -1,5 +1,6 @@ using System.Collections.Concurrent; using System.Net.WebSockets; +using DysonNetwork.Shared.Proto; namespace DysonNetwork.Pusher.Connection; @@ -13,7 +14,7 @@ public class WebSocketService } private static readonly ConcurrentDictionary< - (Guid AccountId, string DeviceId), + (string AccountId, string DeviceId), (WebSocket Socket, CancellationTokenSource Cts) > ActiveConnections = new(); @@ -29,21 +30,23 @@ public class WebSocketService ActiveSubscriptions.TryRemove(deviceId, out _); } - public bool IsUserSubscribedToChatRoom(Guid accountId, string chatRoomId) + public bool IsUserSubscribedToChatRoom(string accountId, string chatRoomId) { var userDeviceIds = ActiveConnections.Keys.Where(k => k.AccountId == accountId).Select(k => k.DeviceId); foreach (var deviceId in userDeviceIds) { - if (ActiveSubscriptions.TryGetValue(deviceId, out var subscribedChatRoomId) && subscribedChatRoomId == chatRoomId) + if (ActiveSubscriptions.TryGetValue(deviceId, out var subscribedChatRoomId) && + subscribedChatRoomId == chatRoomId) { return true; } } + return false; } public bool TryAdd( - (Guid AccountId, string DeviceId) key, + (string AccountId, string DeviceId) key, WebSocket socket, CancellationTokenSource cts ) @@ -54,7 +57,7 @@ public class WebSocketService return ActiveConnections.TryAdd(key, (socket, cts)); } - public void Disconnect((Guid AccountId, string DeviceId) key, string? reason = null) + public void Disconnect((string AccountId, string DeviceId) key, string? reason = null) { if (!ActiveConnections.TryGetValue(key, out var data)) return; data.Socket.CloseAsync( @@ -67,12 +70,12 @@ public class WebSocketService UnsubscribeFromChatRoom(key.DeviceId); } - public bool GetAccountIsConnected(Guid accountId) + public bool GetAccountIsConnected(string accountId) { return ActiveConnections.Any(c => c.Key.AccountId == accountId); } - public void SendPacketToAccount(Guid userId, WebSocketPacket packet) + public void SendPacketToAccount(string userId, WebSocketPacket packet) { var connections = ActiveConnections.Where(c => c.Key.AccountId == userId); var packetBytes = packet.ToBytes(); @@ -106,8 +109,12 @@ public class WebSocketService } } - public async Task HandlePacket(Account.Account currentUser, string deviceId, WebSocketPacket packet, - WebSocket socket) + public async Task HandlePacket( + Account currentUser, + string deviceId, + WebSocketPacket packet, + WebSocket socket + ) { if (_handlerMap.TryGetValue(packet.Type, out var handler)) { diff --git a/DysonNetwork.Pusher/DysonNetwork.Pusher.csproj b/DysonNetwork.Pusher/DysonNetwork.Pusher.csproj index f3fcc72..f2639b7 100644 --- a/DysonNetwork.Pusher/DysonNetwork.Pusher.csproj +++ b/DysonNetwork.Pusher/DysonNetwork.Pusher.csproj @@ -8,8 +8,19 @@ + + + + + + + + + + + @@ -18,4 +29,8 @@ + + + + diff --git a/DysonNetwork.Pusher/Email/EmailService.cs b/DysonNetwork.Pusher/Email/EmailService.cs new file mode 100644 index 0000000..6ebe5d3 --- /dev/null +++ b/DysonNetwork.Pusher/Email/EmailService.cs @@ -0,0 +1,86 @@ +using MailKit.Net.Smtp; +using MimeKit; + +namespace DysonNetwork.Pusher.Email; + +public class EmailServiceConfiguration +{ + public string Server { get; set; } = null!; + public int Port { get; set; } + public bool UseSsl { get; set; } + public string Username { get; set; } = null!; + public string Password { get; set; } = null!; + public string FromAddress { get; set; } = null!; + public string FromName { get; set; } = null!; + public string SubjectPrefix { get; set; } = null!; +} + +public class EmailService +{ + private readonly EmailServiceConfiguration _configuration; + private readonly ILogger _logger; + + public EmailService(IConfiguration configuration, ILogger logger) + { + var cfg = configuration.GetSection("Email").Get(); + _configuration = cfg ?? throw new ArgumentException("Email service was not configured."); + _logger = logger; + } + + public async Task SendEmailAsync(string? recipientName, string recipientEmail, string subject, string textBody) + { + await SendEmailAsync(recipientName, recipientEmail, subject, textBody, null); + } + + public async Task SendEmailAsync(string? recipientName, string recipientEmail, string subject, string textBody, + string? htmlBody) + { + subject = $"[{_configuration.SubjectPrefix}] {subject}"; + + var emailMessage = new MimeMessage(); + emailMessage.From.Add(new MailboxAddress(_configuration.FromName, _configuration.FromAddress)); + emailMessage.To.Add(new MailboxAddress(recipientName, recipientEmail)); + emailMessage.Subject = subject; + + var bodyBuilder = new BodyBuilder + { + TextBody = textBody + }; + + if (!string.IsNullOrEmpty(htmlBody)) + bodyBuilder.HtmlBody = htmlBody; + + emailMessage.Body = bodyBuilder.ToMessageBody(); + + using var client = new SmtpClient(); + await client.ConnectAsync(_configuration.Server, _configuration.Port, _configuration.UseSsl); + await client.AuthenticateAsync(_configuration.Username, _configuration.Password); + await client.SendAsync(emailMessage); + await client.DisconnectAsync(true); + } + + private static string _ConvertHtmlToPlainText(string html) + { + // Remove style tags and their contents + html = System.Text.RegularExpressions.Regex.Replace(html, "]*>.*?", "", + System.Text.RegularExpressions.RegexOptions.Singleline); + + // Replace header tags with text + newlines + html = System.Text.RegularExpressions.Regex.Replace(html, "]*>(.*?)", "$1\n\n", + System.Text.RegularExpressions.RegexOptions.IgnoreCase); + + // Replace line breaks + html = html.Replace("
", "\n").Replace("
", "\n").Replace("
", "\n"); + + // Remove all remaining HTML tags + html = System.Text.RegularExpressions.Regex.Replace(html, "<[^>]+>", ""); + + // Decode HTML entities + html = System.Net.WebUtility.HtmlDecode(html); + + // Remove excess whitespace + html = System.Text.RegularExpressions.Regex.Replace(html, @"\s+", " ").Trim(); + + return html; + } +} \ No newline at end of file diff --git a/DysonNetwork.Pusher/Notification/Notification.cs b/DysonNetwork.Pusher/Notification/Notification.cs new file mode 100644 index 0000000..3df46e5 --- /dev/null +++ b/DysonNetwork.Pusher/Notification/Notification.cs @@ -0,0 +1,24 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Text.Json.Serialization; +using DysonNetwork.Shared.Data; +using DysonNetwork.Shared.Proto; +using NodaTime; + +namespace DysonNetwork.Pusher.Notification; + +public class Notification : ModelBase +{ + public Guid Id { get; set; } = Guid.NewGuid(); + [MaxLength(1024)] public string Topic { get; set; } = null!; + [MaxLength(1024)] public string? Title { get; set; } + [MaxLength(2048)] public string? Subtitle { get; set; } + [MaxLength(4096)] public string? Content { get; set; } + [Column(TypeName = "jsonb")] public Dictionary? Meta { get; set; } + public int Priority { get; set; } = 10; + public Instant? ViewedAt { get; set; } + + public Guid AccountId { get; set; } + [JsonIgnore] public Account Account { get; set; } = null!; +} + diff --git a/DysonNetwork.Pusher/Notification/PushService.cs b/DysonNetwork.Pusher/Notification/PushService.cs new file mode 100644 index 0000000..3d8b2d1 --- /dev/null +++ b/DysonNetwork.Pusher/Notification/PushService.cs @@ -0,0 +1,258 @@ +using System.Text; +using System.Text.Json; +using DysonNetwork.Shared.Proto; +using EFCore.BulkExtensions; +using Microsoft.EntityFrameworkCore; +using NodaTime; + +namespace DysonNetwork.Pusher.Notification; + +public class PushService(IConfiguration config, AppDatabase db, IHttpClientFactory httpFactory) +{ + private readonly string _notifyTopic = config["Notifications:Topic"]!; + private readonly Uri _notifyEndpoint = new(config["Notifications:Endpoint"]!); + + public async Task UnsubscribePushNotifications(string deviceId) + { + await db.PushSubscriptions + .Where(s => s.DeviceId == deviceId) + .ExecuteDeleteAsync(); + } + + public async Task SubscribePushNotification( + string deviceId, + string deviceToken, + PushProvider provider, + Account account + ) + { + var now = SystemClock.Instance.GetCurrentInstant(); + + // First check if a matching subscription exists + var accountId = Guid.Parse(account.Id!); + var existingSubscription = await db.PushSubscriptions + .Where(s => s.AccountId == accountId) + .Where(s => s.DeviceId == deviceId || s.DeviceToken == deviceToken) + .FirstOrDefaultAsync(); + + if (existingSubscription is not null) + { + // Update the existing subscription directly in the database + await db.PushSubscriptions + .Where(s => s.Id == existingSubscription.Id) + .ExecuteUpdateAsync(setters => setters + .SetProperty(s => s.DeviceId, deviceId) + .SetProperty(s => s.DeviceToken, deviceToken) + .SetProperty(s => s.UpdatedAt, now)); + + // Return the updated subscription + existingSubscription.DeviceId = deviceId; + existingSubscription.DeviceToken = deviceToken; + existingSubscription.UpdatedAt = now; + return existingSubscription; + } + + var subscription = new PushSubscription + { + DeviceId = deviceId, + DeviceToken = deviceToken, + Provider = provider, + AccountId = accountId, + }; + + db.PushSubscriptions.Add(subscription); + await db.SaveChangesAsync(); + + return subscription; + } + + public async Task SendNotification( + Account account, + string topic, + string? title = null, + string? subtitle = null, + string? content = null, + Dictionary? meta = null, + string? actionUri = null, + bool isSilent = false, + bool save = true + ) + { + if (title is null && subtitle is null && content is null) + throw new ArgumentException("Unable to send notification that completely empty."); + + meta ??= new Dictionary(); + if (actionUri is not null) meta["action_uri"] = actionUri; + + var accountId = Guid.Parse(account.Id!); + var notification = new Notification + { + Topic = topic, + Title = title, + Subtitle = subtitle, + Content = content, + Meta = meta, + AccountId = accountId, + }; + + if (save) + { + db.Add(notification); + await db.SaveChangesAsync(); + } + + if (!isSilent) _ = DeliveryNotification(notification); + + return notification; + } + + public async Task DeliveryNotification(Pusher.Notification.Notification notification) + { + // Pushing the notification + var subscribers = await db.PushSubscriptions + .Where(s => s.AccountId == notification.AccountId) + .ToListAsync(); + + await _PushNotification(notification, subscribers); + } + + public async Task MarkNotificationsViewed(ICollection notifications) + { + var now = SystemClock.Instance.GetCurrentInstant(); + var id = notifications.Where(n => n.ViewedAt == null).Select(n => n.Id).ToList(); + if (id.Count == 0) return; + + await db.Notifications + .Where(n => id.Contains(n.Id)) + .ExecuteUpdateAsync(s => s.SetProperty(n => n.ViewedAt, now) + ); + } + + public async Task SendNotificationBatch(Notification notification, List accounts, bool save = false) + { + if (save) + { + var notifications = accounts.Select(x => + { + var newNotification = new Notification + { + Topic = notification.Topic, + Title = notification.Title, + Subtitle = notification.Subtitle, + Content = notification.Content, + Meta = notification.Meta, + Priority = notification.Priority, + Account = x, + AccountId = Guid.Parse(x.Id) + }; + return newNotification; + }).ToList(); + await db.BulkInsertAsync(notifications); + } + + foreach (var account in accounts) + { + notification.Account = account; + notification.AccountId = Guid.Parse(account.Id); + } + + var accountsId = accounts.Select(x => Guid.Parse(x.Id)).ToList(); + var subscribers = await db.PushSubscriptions + .Where(s => accountsId.Contains(s.AccountId)) + .ToListAsync(); + await _PushNotification(notification, subscribers); + } + + private List> _BuildNotificationPayload(Notification notification, + IEnumerable subscriptions) + { + var subDict = subscriptions + .GroupBy(x => x.Provider) + .ToDictionary(x => x.Key, x => x.ToList()); + + var notifications = subDict.Select(value => + { + var platformCode = value.Key switch + { + PushProvider.Apple => 1, + PushProvider.Google => 2, + _ => throw new InvalidOperationException($"Unknown push provider: {value.Key}") + }; + + var tokens = value.Value.Select(x => x.DeviceToken).ToList(); + return _BuildNotificationPayload(notification, platformCode, tokens); + }).ToList(); + + return notifications.ToList(); + } + + private Dictionary _BuildNotificationPayload(Pusher.Notification.Notification notification, + int platformCode, + IEnumerable deviceTokens) + { + var alertDict = new Dictionary(); + var dict = new Dictionary + { + ["notif_id"] = notification.Id.ToString(), + ["apns_id"] = notification.Id.ToString(), + ["topic"] = _notifyTopic, + ["tokens"] = deviceTokens, + ["data"] = new Dictionary + { + ["type"] = notification.Topic, + ["meta"] = notification.Meta ?? new Dictionary(), + }, + ["mutable_content"] = true, + ["priority"] = notification.Priority >= 5 ? "high" : "normal", + }; + + if (!string.IsNullOrWhiteSpace(notification.Title)) + { + dict["title"] = notification.Title; + alertDict["title"] = notification.Title; + } + + if (!string.IsNullOrWhiteSpace(notification.Content)) + { + dict["message"] = notification.Content; + alertDict["body"] = notification.Content; + } + + if (!string.IsNullOrWhiteSpace(notification.Subtitle)) + { + dict["message"] = $"{notification.Subtitle}\n{dict["message"]}"; + alertDict["subtitle"] = notification.Subtitle; + } + + if (notification.Priority >= 5) + dict["name"] = "default"; + + dict["platform"] = platformCode; + dict["alert"] = alertDict; + + return dict; + } + + private async Task _PushNotification( + Notification notification, + IEnumerable subscriptions + ) + { + var subList = subscriptions.ToList(); + if (subList.Count == 0) return; + + var requestDict = new Dictionary + { + ["notifications"] = _BuildNotificationPayload(notification, subList) + }; + + var client = httpFactory.CreateClient(); + client.BaseAddress = _notifyEndpoint; + var request = await client.PostAsync("/push", new StringContent( + JsonSerializer.Serialize(requestDict), + Encoding.UTF8, + "application/json" + )); + request.EnsureSuccessStatusCode(); + } +} \ No newline at end of file diff --git a/DysonNetwork.Pusher/Notification/PushSubscription.cs b/DysonNetwork.Pusher/Notification/PushSubscription.cs new file mode 100644 index 0000000..1b50367 --- /dev/null +++ b/DysonNetwork.Pusher/Notification/PushSubscription.cs @@ -0,0 +1,25 @@ +using System.ComponentModel.DataAnnotations; +using DysonNetwork.Shared.Data; +using Microsoft.EntityFrameworkCore; +using NodaTime; + +namespace DysonNetwork.Pusher.Notification; + +public enum PushProvider +{ + Apple, + Google +} + +[Index(nameof(AccountId), nameof(DeviceId), nameof(DeletedAt), IsUnique = true)] +public class PushSubscription : ModelBase +{ + public Guid Id { get; set; } = Guid.NewGuid(); + public Guid AccountId { get; set; } + [MaxLength(8192)] public string DeviceId { get; set; } = null!; + [MaxLength(8192)] public string DeviceToken { get; set; } = null!; + public PushProvider Provider { get; set; } + + public int CountDelivered { get; set; } + public Instant? LastUsedAt { get; set; } +} \ No newline at end of file diff --git a/DysonNetwork.Shared/DysonNetwork.Shared.csproj b/DysonNetwork.Shared/DysonNetwork.Shared.csproj index 364b03b..5d993e2 100644 --- a/DysonNetwork.Shared/DysonNetwork.Shared.csproj +++ b/DysonNetwork.Shared/DysonNetwork.Shared.csproj @@ -20,6 +20,7 @@ + diff --git a/DysonNetwork.Shared/Proto/GrpcTypeHelper.cs b/DysonNetwork.Shared/Proto/GrpcTypeHelper.cs new file mode 100644 index 0000000..1d2d7bd --- /dev/null +++ b/DysonNetwork.Shared/Proto/GrpcTypeHelper.cs @@ -0,0 +1,89 @@ +using Google.Protobuf.Collections; +using Google.Protobuf.WellKnownTypes; +using Newtonsoft.Json; + +namespace DysonNetwork.Shared.Proto; + +public abstract class GrpcTypeHelper +{ + private static readonly JsonSerializerSettings SerializerSettings = new() + { + PreserveReferencesHandling = PreserveReferencesHandling.All, + ReferenceLoopHandling = ReferenceLoopHandling.Ignore + }; + + public static MapField ConvertToValueMap(Dictionary source) + { + var result = new MapField(); + foreach (var kvp in source) + { + result[kvp.Key] = kvp.Value switch + { + string s => Value.ForString(s), + int i => Value.ForNumber(i), + long l => Value.ForNumber(l), + float f => Value.ForNumber(f), + double d => Value.ForNumber(d), + bool b => Value.ForBool(b), + null => Value.ForNull(), + _ => Value.ForString(JsonConvert.SerializeObject(kvp.Value, SerializerSettings)) // fallback to JSON string + }; + } + return result; + } + + public static Dictionary ConvertFromValueMap(MapField source) + { + var result = new Dictionary(); + foreach (var kvp in source) + { + var value = kvp.Value; + switch (value.KindCase) + { + case Value.KindOneofCase.StringValue: + try + { + // Try to parse as JSON object or primitive + result[kvp.Key] = JsonConvert.DeserializeObject(value.StringValue); + } + catch + { + // Fallback to raw string + result[kvp.Key] = value.StringValue; + } + break; + case Value.KindOneofCase.NumberValue: + result[kvp.Key] = value.NumberValue; + break; + case Value.KindOneofCase.BoolValue: + result[kvp.Key] = value.BoolValue; + break; + case Value.KindOneofCase.NullValue: + result[kvp.Key] = null; + break; + case Value.KindOneofCase.StructValue: + result[kvp.Key] = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(value.StructValue.Fields.ToDictionary(f => f.Key, f => ConvertField(f.Value)), SerializerSettings)); + break; + case Value.KindOneofCase.ListValue: + result[kvp.Key] = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(value.ListValue.Values.Select(ConvertField).ToList(), SerializerSettings)); + break; + default: + result[kvp.Key] = null; + break; + } + } + return result; + } + + private static object? ConvertField(Value value) + { + return value.KindCase switch + { + Value.KindOneofCase.StringValue => value.StringValue, + Value.KindOneofCase.NumberValue => value.NumberValue, + Value.KindOneofCase.BoolValue => value.BoolValue, + Value.KindOneofCase.NullValue => null, + _ => JsonConvert.DeserializeObject(JsonConvert.SerializeObject(value, SerializerSettings)) + }; + } +} \ No newline at end of file diff --git a/DysonNetwork.Shared/Proto/account.proto b/DysonNetwork.Shared/Proto/account.proto index 444cb49..142f52e 100644 --- a/DysonNetwork.Shared/Proto/account.proto +++ b/DysonNetwork.Shared/Proto/account.proto @@ -8,6 +8,7 @@ import "google/protobuf/timestamp.proto"; import "google/protobuf/wrappers.proto"; import "google/protobuf/empty.proto"; import "google/protobuf/field_mask.proto"; +import "google/protobuf/struct.proto"; import 'file.proto'; @@ -83,7 +84,7 @@ message AccountAuthFactor { string id = 1; AccountAuthFactorType type = 2; google.protobuf.StringValue secret = 3; // Omitted from JSON serialization in original - map config = 4; // Omitted from JSON serialization in original + map config = 4; // Omitted from JSON serialization in original int32 trustworthy = 5; google.protobuf.Timestamp enabled_at = 6; google.protobuf.Timestamp expired_at = 7; @@ -107,7 +108,7 @@ message AccountBadge { string type = 2; // Type/category of the badge google.protobuf.StringValue label = 3; // Display name of the badge google.protobuf.StringValue caption = 4; // Optional description of the badge - map meta = 5; // Additional metadata for the badge + map meta = 5; // Additional metadata for the badge google.protobuf.Timestamp activated_at = 6; // When the badge was activated google.protobuf.Timestamp expired_at = 7; // Optional expiration time string account_id = 8; // ID of the account this badge belongs to @@ -118,7 +119,7 @@ message AccountConnection { string id = 1; string provider = 2; string provided_identifier = 3; - map meta = 4; + map meta = 4; google.protobuf.StringValue access_token = 5; // Omitted from JSON serialization google.protobuf.StringValue refresh_token = 6; // Omitted from JSON serialization google.protobuf.Timestamp last_used_at = 7; @@ -127,19 +128,30 @@ message AccountConnection { // VerificationMark represents verification status message VerificationMark { - bool verified = 1; - string method = 2; - google.protobuf.Timestamp verified_at = 3; + VerificationMarkType type = 1; + string title = 2; + string description = 3; string verified_by = 4; } +enum VerificationMarkType { + VERIFICATION_MARK_TYPE_UNSPECIFIED = 0; + OFFICIAL = 1; + INDIVIDUAL = 2; + ORGANIZATION = 3; + GOVERNMENT = 4; + CREATOR = 5; + DEVELOPER = 6; + PARODY = 7; +} + // BadgeReferenceObject represents a reference to a badge with minimal information message BadgeReferenceObject { string id = 1; // Unique identifier for the badge string type = 2; // Type/category of the badge google.protobuf.StringValue label = 3; // Display name of the badge google.protobuf.StringValue caption = 4; // Optional description of the badge - map meta = 5; // Additional metadata for the badge + map meta = 5; // Additional metadata for the badge google.protobuf.Timestamp activated_at = 6; // When the badge was activated google.protobuf.Timestamp expired_at = 7; // Optional expiration time string account_id = 8; // ID of the account this badge belongs to diff --git a/DysonNetwork.Shared/Proto/auth.proto b/DysonNetwork.Shared/Proto/auth.proto new file mode 100644 index 0000000..e96f35e --- /dev/null +++ b/DysonNetwork.Shared/Proto/auth.proto @@ -0,0 +1,68 @@ +syntax = "proto3"; + +package proto; + +option csharp_namespace = "DysonNetwork.Shared.Proto"; + +import "google/protobuf/timestamp.proto"; +import "google/protobuf/wrappers.proto"; + +// Represents a user session +message AuthSession { + string id = 1; + google.protobuf.StringValue label = 2; + google.protobuf.Timestamp last_granted_at = 3; + google.protobuf.Timestamp expired_at = 4; + string account_id = 5; + string challenge_id = 6; + AuthChallenge challenge = 7; + google.protobuf.StringValue app_id = 8; +} + +// Represents an authentication challenge +message AuthChallenge { + string id = 1; + google.protobuf.Timestamp expired_at = 2; + int32 step_remain = 3; + int32 step_total = 4; + int32 failed_attempts = 5; + ChallengePlatform platform = 6; + ChallengeType type = 7; + repeated string blacklist_factors = 8; + repeated string audiences = 9; + repeated string scopes = 10; + google.protobuf.StringValue ip_address = 11; + google.protobuf.StringValue user_agent = 12; + google.protobuf.StringValue device_id = 13; + google.protobuf.StringValue nonce = 14; + // Point location is omitted as there is no direct proto equivalent. + string account_id = 15; +} + +// Enum for challenge types +enum ChallengeType { + CHALLENGE_TYPE_UNSPECIFIED = 0; + LOGIN = 1; + OAUTH = 2; + OIDC = 3; +} + +// Enum for challenge platforms +enum ChallengePlatform { + CHALLENGE_PLATFORM_UNSPECIFIED = 0; + UNIDENTIFIED = 1; + WEB = 2; + IOS = 3; + ANDROID = 4; + MACOS = 5; + WINDOWS = 6; + LINUX = 7; +} + +service AuthService { + rpc Authenticate(AuthenticateRequest) returns (AuthSession) {} +} + +message AuthenticateRequest { + string token = 1; +} diff --git a/DysonNetwork.Sphere/DysonNetwork.Sphere.csproj b/DysonNetwork.Sphere/DysonNetwork.Sphere.csproj index 7532645..1c05644 100644 --- a/DysonNetwork.Sphere/DysonNetwork.Sphere.csproj +++ b/DysonNetwork.Sphere/DysonNetwork.Sphere.csproj @@ -26,7 +26,7 @@ - + diff --git a/DysonNetwork.sln.DotSettings.user b/DysonNetwork.sln.DotSettings.user index 069ceae..41ec08d 100644 --- a/DysonNetwork.sln.DotSettings.user +++ b/DysonNetwork.sln.DotSettings.user @@ -1,5 +1,6 @@  ForceIncluded + ForceIncluded ForceIncluded ForceIncluded ForceIncluded @@ -43,6 +44,7 @@ ForceIncluded ForceIncluded ForceIncluded + ForceIncluded ForceIncluded ForceIncluded ForceIncluded From 4a7f2e18b32bca7fcf552b8700097f7d0ac47594 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Sat, 12 Jul 2025 23:31:21 +0800 Subject: [PATCH 06/42] :sparkles: Pusher service --- DysonNetwork.Pass/Account/ActionLogService.cs | 2 +- DysonNetwork.Pass/Auth/AuthController.cs | 2 +- DysonNetwork.Pass/Auth/AuthServiceGrpc.cs | 4 +- .../Startup/ApplicationConfiguration.cs | 2 + .../Startup/ServiceCollectionExtensions.cs | 3 +- DysonNetwork.Pass/WeatherForecast.cs | 12 -- .../DysonNetwork.Pusher.csproj | 1 + .../Notification/PushService.cs | 22 +-- DysonNetwork.Pusher/Program.cs | 40 +++-- .../Services/PusherServiceGrpc.cs | 162 ++++++++++++++++++ .../Startup/ApplicationConfiguration.cs | 70 ++++++++ .../Startup/KestrelConfiguration.cs | 17 ++ .../Startup/ScheduledJobsConfiguration.cs | 22 +++ .../Startup/ServiceCollectionExtensions.cs | 138 +++++++++++++++ .../{Geo => GeoIp}/GeoIpService.cs | 2 +- DysonNetwork.Shared/Proto/pusher.proto | 110 ++++++++++++ 16 files changed, 563 insertions(+), 46 deletions(-) delete mode 100644 DysonNetwork.Pass/WeatherForecast.cs create mode 100644 DysonNetwork.Pusher/Services/PusherServiceGrpc.cs create mode 100644 DysonNetwork.Pusher/Startup/ApplicationConfiguration.cs create mode 100644 DysonNetwork.Pusher/Startup/KestrelConfiguration.cs create mode 100644 DysonNetwork.Pusher/Startup/ScheduledJobsConfiguration.cs create mode 100644 DysonNetwork.Pusher/Startup/ServiceCollectionExtensions.cs rename DysonNetwork.Shared/{Geo => GeoIp}/GeoIpService.cs (97%) create mode 100644 DysonNetwork.Shared/Proto/pusher.proto diff --git a/DysonNetwork.Pass/Account/ActionLogService.cs b/DysonNetwork.Pass/Account/ActionLogService.cs index b5ca0b4..2dff230 100644 --- a/DysonNetwork.Pass/Account/ActionLogService.cs +++ b/DysonNetwork.Pass/Account/ActionLogService.cs @@ -1,5 +1,5 @@ using DysonNetwork.Shared.Cache; -using DysonNetwork.Shared.Geo; +using DysonNetwork.Shared.GeoIp; namespace DysonNetwork.Pass.Account; diff --git a/DysonNetwork.Pass/Auth/AuthController.cs b/DysonNetwork.Pass/Auth/AuthController.cs index ab38462..9722004 100644 --- a/DysonNetwork.Pass/Auth/AuthController.cs +++ b/DysonNetwork.Pass/Auth/AuthController.cs @@ -3,7 +3,7 @@ using Microsoft.AspNetCore.Mvc; using NodaTime; using Microsoft.EntityFrameworkCore; using DysonNetwork.Pass.Account; -using DysonNetwork.Shared.Geo; +using DysonNetwork.Shared.GeoIp; namespace DysonNetwork.Pass.Auth; diff --git a/DysonNetwork.Pass/Auth/AuthServiceGrpc.cs b/DysonNetwork.Pass/Auth/AuthServiceGrpc.cs index 208bceb..0e023ad 100644 --- a/DysonNetwork.Pass/Auth/AuthServiceGrpc.cs +++ b/DysonNetwork.Pass/Auth/AuthServiceGrpc.cs @@ -4,9 +4,9 @@ using Microsoft.EntityFrameworkCore; namespace DysonNetwork.Pass.Auth; -public class AuthServiceGrpc(AuthService authService, AppDatabase db) : Shared.Proto.AuthService +public class AuthServiceGrpc(AuthService authService, AppDatabase db) : Shared.Proto.AuthService.AuthServiceBase { - public async Task Authenticate(AuthenticateRequest request, ServerCallContext context) + public override async Task Authenticate(AuthenticateRequest request, ServerCallContext context) { if (!authService.ValidateToken(request.Token, out var sessionId)) { diff --git a/DysonNetwork.Pass/Startup/ApplicationConfiguration.cs b/DysonNetwork.Pass/Startup/ApplicationConfiguration.cs index d997ce4..8ee7fa8 100644 --- a/DysonNetwork.Pass/Startup/ApplicationConfiguration.cs +++ b/DysonNetwork.Pass/Startup/ApplicationConfiguration.cs @@ -1,5 +1,6 @@ using System.Net; using DysonNetwork.Pass.Account; +using DysonNetwork.Pass.Auth; using DysonNetwork.Pass.Permission; using Microsoft.AspNetCore.HttpOverrides; using Prometheus; @@ -68,6 +69,7 @@ public static class ApplicationConfiguration public static WebApplication ConfigureGrpcServices(this WebApplication app) { app.MapGrpcService(); + app.MapGrpcService(); return app; } diff --git a/DysonNetwork.Pass/Startup/ServiceCollectionExtensions.cs b/DysonNetwork.Pass/Startup/ServiceCollectionExtensions.cs index eec3b8a..af8e0f9 100644 --- a/DysonNetwork.Pass/Startup/ServiceCollectionExtensions.cs +++ b/DysonNetwork.Pass/Startup/ServiceCollectionExtensions.cs @@ -18,7 +18,7 @@ using DysonNetwork.Pass.Auth.OidcProvider.Services; using DysonNetwork.Pass.Handlers; using DysonNetwork.Pass.Wallet.PaymentHandlers; using DysonNetwork.Shared.Cache; -using DysonNetwork.Shared.Geo; +using DysonNetwork.Shared.GeoIp; namespace DysonNetwork.Pass.Startup; @@ -53,6 +53,7 @@ public static class ServiceCollectionExtensions // Register gRPC services services.AddScoped(); + services.AddScoped(); // Register OIDC services services.AddScoped(); diff --git a/DysonNetwork.Pass/WeatherForecast.cs b/DysonNetwork.Pass/WeatherForecast.cs deleted file mode 100644 index 74ce1c5..0000000 --- a/DysonNetwork.Pass/WeatherForecast.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace DysonNetwork.Pass; - -public class WeatherForecast -{ - public DateOnly Date { get; set; } - - public int TemperatureC { get; set; } - - public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); - - public string? Summary { get; set; } -} diff --git a/DysonNetwork.Pusher/DysonNetwork.Pusher.csproj b/DysonNetwork.Pusher/DysonNetwork.Pusher.csproj index f2639b7..718f65d 100644 --- a/DysonNetwork.Pusher/DysonNetwork.Pusher.csproj +++ b/DysonNetwork.Pusher/DysonNetwork.Pusher.csproj @@ -19,6 +19,7 @@ + diff --git a/DysonNetwork.Pusher/Notification/PushService.cs b/DysonNetwork.Pusher/Notification/PushService.cs index 3d8b2d1..050107e 100644 --- a/DysonNetwork.Pusher/Notification/PushService.cs +++ b/DysonNetwork.Pusher/Notification/PushService.cs @@ -66,8 +66,7 @@ public class PushService(IConfiguration config, AppDatabase db, IHttpClientFacto return subscription; } - public async Task SendNotification( - Account account, + public async Task SendNotification(Account account, string topic, string? title = null, string? subtitle = null, @@ -75,8 +74,7 @@ public class PushService(IConfiguration config, AppDatabase db, IHttpClientFacto Dictionary? meta = null, string? actionUri = null, bool isSilent = false, - bool save = true - ) + bool save = true) { if (title is null && subtitle is null && content is null) throw new ArgumentException("Unable to send notification that completely empty."); @@ -102,8 +100,6 @@ public class PushService(IConfiguration config, AppDatabase db, IHttpClientFacto } if (!isSilent) _ = DeliveryNotification(notification); - - return notification; } public async Task DeliveryNotification(Pusher.Notification.Notification notification) @@ -128,7 +124,7 @@ public class PushService(IConfiguration config, AppDatabase db, IHttpClientFacto ); } - public async Task SendNotificationBatch(Notification notification, List accounts, bool save = false) + public async Task SendNotificationBatch(Notification notification, List accounts, bool save = false) { if (save) { @@ -142,23 +138,15 @@ public class PushService(IConfiguration config, AppDatabase db, IHttpClientFacto Content = notification.Content, Meta = notification.Meta, Priority = notification.Priority, - Account = x, - AccountId = Guid.Parse(x.Id) + AccountId = x }; return newNotification; }).ToList(); await db.BulkInsertAsync(notifications); } - foreach (var account in accounts) - { - notification.Account = account; - notification.AccountId = Guid.Parse(account.Id); - } - - var accountsId = accounts.Select(x => Guid.Parse(x.Id)).ToList(); var subscribers = await db.PushSubscriptions - .Where(s => accountsId.Contains(s.AccountId)) + .Where(s => accounts.Contains(s.AccountId)) .ToListAsync(); await _PushNotification(notification, subscribers); } diff --git a/DysonNetwork.Pusher/Program.cs b/DysonNetwork.Pusher/Program.cs index 7688653..744c79d 100644 --- a/DysonNetwork.Pusher/Program.cs +++ b/DysonNetwork.Pusher/Program.cs @@ -1,23 +1,41 @@ +using DysonNetwork.Pass.Startup; +using DysonNetwork.Pusher; +using DysonNetwork.Pusher.Startup; +using Microsoft.EntityFrameworkCore; + var builder = WebApplication.CreateBuilder(args); -// Add services to the container. +// Configure Kestrel and server options +builder.ConfigureAppKestrel(); -builder.Services.AddControllers(); -// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi -builder.Services.AddOpenApi(); +// Add application services +builder.Services.AddAppServices(builder.Configuration); +builder.Services.AddAppRateLimiting(); +builder.Services.AddAppAuthentication(); +builder.Services.AddAppSwagger(); + +// Add flush handlers and websocket handlers +builder.Services.AddAppFlushHandlers(); + +// Add business services +builder.Services.AddAppBusinessServices(); + +// Add scheduled jobs +builder.Services.AddAppScheduledJobs(); var app = builder.Build(); -// Configure the HTTP request pipeline. -if (app.Environment.IsDevelopment()) +// Run database migrations +using (var scope = app.Services.CreateScope()) { - app.MapOpenApi(); + var db = scope.ServiceProvider.GetRequiredService(); + await db.Database.MigrateAsync(); } -app.UseHttpsRedirection(); +// Configure application middleware pipeline +app.ConfigureAppMiddleware(builder.Configuration); -app.UseAuthorization(); - -app.MapControllers(); +// Configure gRPC +app.ConfigureGrpcServices(); app.Run(); \ No newline at end of file diff --git a/DysonNetwork.Pusher/Services/PusherServiceGrpc.cs b/DysonNetwork.Pusher/Services/PusherServiceGrpc.cs new file mode 100644 index 0000000..a4c87ef --- /dev/null +++ b/DysonNetwork.Pusher/Services/PusherServiceGrpc.cs @@ -0,0 +1,162 @@ +using DysonNetwork.Pusher.Connection; +using DysonNetwork.Pusher.Email; +using DysonNetwork.Pusher.Notification; +using DysonNetwork.Shared.Proto; +using Google.Protobuf.WellKnownTypes; +using Grpc.Core; + +namespace DysonNetwork.Pusher.Services; + +public class PusherServiceGrpc( + EmailService emailService, + WebSocketService webSocketService, + PushService pushService +) : PusherService.PusherServiceBase +{ + public override async Task SendEmail(SendEmailRequest request, ServerCallContext context) + { + await emailService.SendEmailAsync( + request.Email.ToName, + request.Email.ToAddress, + request.Email.Subject, + request.Email.Body + ); + return new Empty(); + } + + public override Task PushWebSocketPacket(PushWebSocketPacketRequest request, ServerCallContext context) + { + var packet = new Connection.WebSocketPacket + { + Type = request.Packet.Type, + Data = request.Packet.Data, + ErrorMessage = request.Packet.ErrorMessage + }; + webSocketService.SendPacketToAccount(request.UserId, packet); + return Task.FromResult(new Empty()); + } + + public override Task PushWebSocketPacketToUsers(PushWebSocketPacketToUsersRequest request, + ServerCallContext context) + { + var packet = new Connection.WebSocketPacket + { + Type = request.Packet.Type, + Data = request.Packet.Data, + ErrorMessage = request.Packet.ErrorMessage + }; + foreach (var userId in request.UserIds) + webSocketService.SendPacketToAccount(userId, packet); + + return Task.FromResult(new Empty()); + } + + public override Task PushWebSocketPacketToDevice(PushWebSocketPacketToDeviceRequest request, + ServerCallContext context) + { + var packet = new Connection.WebSocketPacket + { + Type = request.Packet.Type, + Data = request.Packet.Data, + ErrorMessage = request.Packet.ErrorMessage + }; + webSocketService.SendPacketToDevice(request.DeviceId, packet); + return Task.FromResult(new Empty()); + } + + public override Task PushWebSocketPacketToDevices(PushWebSocketPacketToDevicesRequest request, + ServerCallContext context) + { + var packet = new Connection.WebSocketPacket + { + Type = request.Packet.Type, + Data = request.Packet.Data, + ErrorMessage = request.Packet.ErrorMessage + }; + foreach (var deviceId in request.DeviceIds) + webSocketService.SendPacketToDevice(deviceId, packet); + + return Task.FromResult(new Empty()); + } + + public override async Task SendPushNotification(SendPushNotificationRequest request, + ServerCallContext context) + { + // This is a placeholder implementation. In a real-world scenario, you would + // need to retrieve the account from the database based on the device token. + var account = new Account(); + await pushService.SendNotification( + account, + request.Notification.Topic, + request.Notification.Title, + request.Notification.Subtitle, + request.Notification.Body, + GrpcTypeHelper.ConvertFromValueMap(request.Notification.Meta), + request.Notification.ActionUri, + request.Notification.IsSilent, + request.Notification.IsSavable + ); + return new Empty(); + } + + public override async Task SendPushNotificationToDevices(SendPushNotificationToDevicesRequest request, + ServerCallContext context) + { + // This is a placeholder implementation. In a real-world scenario, you would + // need to retrieve the accounts from the database based on the device tokens. + var account = new Account(); + foreach (var deviceId in request.DeviceIds) + { + await pushService.SendNotification( + account, + request.Notification.Topic, + request.Notification.Title, + request.Notification.Subtitle, + request.Notification.Body, + GrpcTypeHelper.ConvertFromValueMap(request.Notification.Meta), + request.Notification.ActionUri, + request.Notification.IsSilent, + request.Notification.IsSavable + ); + } + + return new Empty(); + } + + public override async Task SendPushNotificationToUser(SendPushNotificationToUserRequest request, + ServerCallContext context) + { + // This is a placeholder implementation. In a real-world scenario, you would + // need to retrieve the account from the database based on the user ID. + var account = new Account(); + await pushService.SendNotification( + account, + request.Notification.Topic, + request.Notification.Title, + request.Notification.Subtitle, + request.Notification.Body, + GrpcTypeHelper.ConvertFromValueMap(request.Notification.Meta), + request.Notification.ActionUri, + request.Notification.IsSilent, + request.Notification.IsSavable + ); + return new Empty(); + } + + public override async Task SendPushNotificationToUsers(SendPushNotificationToUsersRequest request, + ServerCallContext context) + { + var notification = new Notification.Notification + { + Topic = request.Notification.Topic, + Title = request.Notification.Title, + Subtitle = request.Notification.Subtitle, Content = request.Notification.Body, + Meta = GrpcTypeHelper.ConvertFromValueMap(request.Notification.Meta), + }; + if (request.Notification.ActionUri is not null) + notification.Meta["action_uri"] = request.Notification.ActionUri; + var accounts = request.UserIds.Select(Guid.Parse).ToList(); + await pushService.SendNotificationBatch(notification, accounts, request.Notification.IsSavable); + return new Empty(); + } +} \ No newline at end of file diff --git a/DysonNetwork.Pusher/Startup/ApplicationConfiguration.cs b/DysonNetwork.Pusher/Startup/ApplicationConfiguration.cs new file mode 100644 index 0000000..8b98dad --- /dev/null +++ b/DysonNetwork.Pusher/Startup/ApplicationConfiguration.cs @@ -0,0 +1,70 @@ +using System.Net; +using DysonNetwork.Pusher.Services; +using Microsoft.AspNetCore.HttpOverrides; + +namespace DysonNetwork.Pusher.Startup; + +public static class ApplicationConfiguration +{ + public static WebApplication ConfigureAppMiddleware(this WebApplication app, IConfiguration configuration) + { + app.MapOpenApi(); + + app.UseSwagger(); + app.UseSwaggerUI(); + + app.UseRequestLocalization(); + + ConfigureForwardedHeaders(app, configuration); + + app.UseCors(opts => + opts.SetIsOriginAllowed(_ => true) + .WithExposedHeaders("*") + .WithHeaders() + .AllowCredentials() + .AllowAnyHeader() + .AllowAnyMethod() + ); + + app.UseWebSockets(); + app.UseRateLimiter(); + app.UseHttpsRedirection(); + app.UseAuthentication(); + app.UseAuthorization(); + + app.MapControllers().RequireRateLimiting("fixed"); + app.MapStaticAssets().RequireRateLimiting("fixed"); + app.MapRazorPages().RequireRateLimiting("fixed"); + + return app; + } + + private static void ConfigureForwardedHeaders(WebApplication app, IConfiguration configuration) + { + var knownProxiesSection = configuration.GetSection("KnownProxies"); + var forwardedHeadersOptions = new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.All }; + + if (knownProxiesSection.Exists()) + { + var proxyAddresses = knownProxiesSection.Get(); + if (proxyAddresses != null) + foreach (var proxy in proxyAddresses) + if (IPAddress.TryParse(proxy, out var ipAddress)) + forwardedHeadersOptions.KnownProxies.Add(ipAddress); + } + else + { + forwardedHeadersOptions.KnownProxies.Add(IPAddress.Any); + forwardedHeadersOptions.KnownProxies.Add(IPAddress.IPv6Any); + } + + app.UseForwardedHeaders(forwardedHeadersOptions); + } + + public static WebApplication ConfigureGrpcServices(this WebApplication app) + { + app.MapGrpcService(); + + return app; + } +} diff --git a/DysonNetwork.Pusher/Startup/KestrelConfiguration.cs b/DysonNetwork.Pusher/Startup/KestrelConfiguration.cs new file mode 100644 index 0000000..b042534 --- /dev/null +++ b/DysonNetwork.Pusher/Startup/KestrelConfiguration.cs @@ -0,0 +1,17 @@ +namespace DysonNetwork.Pass.Startup; + +public static class KestrelConfiguration +{ + public static WebApplicationBuilder ConfigureAppKestrel(this WebApplicationBuilder builder) + { + builder.Host.UseContentRoot(Directory.GetCurrentDirectory()); + builder.WebHost.ConfigureKestrel(options => + { + options.Limits.MaxRequestBodySize = 50 * 1024 * 1024; + options.Limits.KeepAliveTimeout = TimeSpan.FromMinutes(2); + options.Limits.RequestHeadersTimeout = TimeSpan.FromSeconds(30); + }); + + return builder; + } +} diff --git a/DysonNetwork.Pusher/Startup/ScheduledJobsConfiguration.cs b/DysonNetwork.Pusher/Startup/ScheduledJobsConfiguration.cs new file mode 100644 index 0000000..6a9343a --- /dev/null +++ b/DysonNetwork.Pusher/Startup/ScheduledJobsConfiguration.cs @@ -0,0 +1,22 @@ +using Quartz; + +namespace DysonNetwork.Pusher.Startup; + +public static class ScheduledJobsConfiguration +{ + public static IServiceCollection AddAppScheduledJobs(this IServiceCollection services) + { + services.AddQuartz(q => + { + var appDatabaseRecyclingJob = new JobKey("AppDatabaseRecycling"); + q.AddJob(opts => opts.WithIdentity(appDatabaseRecyclingJob)); + q.AddTrigger(opts => opts + .ForJob(appDatabaseRecyclingJob) + .WithIdentity("AppDatabaseRecyclingTrigger") + .WithCronSchedule("0 0 0 * * ?")); + }); + services.AddQuartzHostedService(q => q.WaitForJobsToComplete = true); + + return services; + } +} diff --git a/DysonNetwork.Pusher/Startup/ServiceCollectionExtensions.cs b/DysonNetwork.Pusher/Startup/ServiceCollectionExtensions.cs new file mode 100644 index 0000000..033a98b --- /dev/null +++ b/DysonNetwork.Pusher/Startup/ServiceCollectionExtensions.cs @@ -0,0 +1,138 @@ +using System.Text.Json; +using System.Threading.RateLimiting; +using DysonNetwork.Pusher.Email; +using DysonNetwork.Pusher.Notification; +using DysonNetwork.Pusher.Services; +using DysonNetwork.Shared.Cache; +using Microsoft.AspNetCore.RateLimiting; +using Microsoft.OpenApi.Models; +using NodaTime; +using NodaTime.Serialization.SystemTextJson; +using StackExchange.Redis; + +namespace DysonNetwork.Pusher.Startup; + +public static class ServiceCollectionExtensions +{ + public static IServiceCollection AddAppServices(this IServiceCollection services, IConfiguration configuration) + { + services.AddDbContext(); + services.AddSingleton(_ => + { + var connection = configuration.GetConnectionString("FastRetrieve")!; + return ConnectionMultiplexer.Connect(connection); + }); + services.AddSingleton(SystemClock.Instance); + services.AddHttpContextAccessor(); + services.AddSingleton(); + + services.AddHttpClient(); + + // Register gRPC services + services.AddGrpc(options => + { + options.EnableDetailedErrors = true; // Will be adjusted in Program.cs + options.MaxReceiveMessageSize = 16 * 1024 * 1024; // 16MB + options.MaxSendMessageSize = 16 * 1024 * 1024; // 16MB + }); + + // Register gRPC reflection for service discovery + services.AddGrpc(); + + // Register gRPC services + services.AddScoped(); + + // Register OIDC services + services.AddControllers().AddJsonOptions(options => + { + options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower; + options.JsonSerializerOptions.DictionaryKeyPolicy = JsonNamingPolicy.SnakeCaseLower; + + options.JsonSerializerOptions.ConfigureForNodaTime(DateTimeZoneProviders.Tzdb); + }); + + return services; + } + + public static IServiceCollection AddAppRateLimiting(this IServiceCollection services) + { + services.AddRateLimiter(o => o.AddFixedWindowLimiter(policyName: "fixed", opts => + { + opts.Window = TimeSpan.FromMinutes(1); + opts.PermitLimit = 120; + opts.QueueLimit = 2; + opts.QueueProcessingOrder = QueueProcessingOrder.OldestFirst; + })); + + return services; + } + + public static IServiceCollection AddAppAuthentication(this IServiceCollection services) + { + services.AddCors(); + services.AddAuthorization(); + + return services; + } + + public static IServiceCollection AddAppSwagger(this IServiceCollection services) + { + services.AddEndpointsApiExplorer(); + services.AddSwaggerGen(options => + { + options.SwaggerDoc("v1", new OpenApiInfo + { + Version = "v1", + Title = "Solar Network API", + Description = "An open-source social network", + TermsOfService = new Uri("https://solsynth.dev/terms"), + License = new OpenApiLicense + { + Name = "APGLv3", + Url = new Uri("https://www.gnu.org/licenses/agpl-3.0.html") + } + }); + options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme + { + In = ParameterLocation.Header, + Description = "Please enter a valid token", + Name = "Authorization", + Type = SecuritySchemeType.Http, + BearerFormat = "JWT", + Scheme = "Bearer" + }); + options.AddSecurityRequirement(new OpenApiSecurityRequirement + { + { + new OpenApiSecurityScheme + { + Reference = new OpenApiReference + { + Type = ReferenceType.SecurityScheme, + Id = "Bearer" + } + }, + [] + } + }); + }); + services.AddOpenApi(); + + return services; + } + + public static IServiceCollection AddAppFlushHandlers(this IServiceCollection services) + { + services.AddSingleton(); + + return services; + } + + public static IServiceCollection AddAppBusinessServices(this IServiceCollection services) + { + services.AddScoped(); + services.AddScoped(); + + return services; + } +} \ No newline at end of file diff --git a/DysonNetwork.Shared/Geo/GeoIpService.cs b/DysonNetwork.Shared/GeoIp/GeoIpService.cs similarity index 97% rename from DysonNetwork.Shared/Geo/GeoIpService.cs rename to DysonNetwork.Shared/GeoIp/GeoIpService.cs index 9507bf2..88803c8 100644 --- a/DysonNetwork.Shared/Geo/GeoIpService.cs +++ b/DysonNetwork.Shared/GeoIp/GeoIpService.cs @@ -3,7 +3,7 @@ using Microsoft.Extensions.Options; using NetTopologySuite.Geometries; using Point = NetTopologySuite.Geometries.Point; -namespace DysonNetwork.Shared.Geo; +namespace DysonNetwork.Shared.GeoIp; public class GeoIpOptions { diff --git a/DysonNetwork.Shared/Proto/pusher.proto b/DysonNetwork.Shared/Proto/pusher.proto new file mode 100644 index 0000000..cf5d815 --- /dev/null +++ b/DysonNetwork.Shared/Proto/pusher.proto @@ -0,0 +1,110 @@ +syntax = "proto3"; + +package proto; + +option csharp_namespace = "DysonNetwork.Shared.Proto"; + +import "google/protobuf/struct.proto"; +import "google/protobuf/empty.proto"; +import "google/protobuf/wrappers.proto"; + +// PusherService provides methods to send various types of notifications. +service PusherService { + // Sends an email. + rpc SendEmail(SendEmailRequest) returns (google.protobuf.Empty) {} + + // Pushes a packet to a user via WebSocket. + rpc PushWebSocketPacket(PushWebSocketPacketRequest) returns (google.protobuf.Empty) {} + + // Pushes a packet to a list of users via WebSocket. + rpc PushWebSocketPacketToUsers(PushWebSocketPacketToUsersRequest) returns (google.protobuf.Empty) {} + + // Pushes a packet to a device via WebSocket. + rpc PushWebSocketPacketToDevice(PushWebSocketPacketToDeviceRequest) returns (google.protobuf.Empty) {} + + // Pushes a packet to a list of devices via WebSocket. + rpc PushWebSocketPacketToDevices(PushWebSocketPacketToDevicesRequest) returns (google.protobuf.Empty) {} + + // Sends a push notification to a device. + rpc SendPushNotification(SendPushNotificationRequest) returns (google.protobuf.Empty) {} + + // Sends a push notification to a list of devices. + rpc SendPushNotificationToDevices(SendPushNotificationToDevicesRequest) returns (google.protobuf.Empty) {} + + // Sends a push notification to a user. + rpc SendPushNotificationToUser(SendPushNotificationToUserRequest) returns (google.protobuf.Empty) {} + + // Sends a push notification to a list of users. + rpc SendPushNotificationToUsers(SendPushNotificationToUsersRequest) returns (google.protobuf.Empty) {} +} + +// Represents an email message. +message EmailMessage { + string to_name = 1; + string to_address = 2; + string subject = 3; + string body = 4; +} + +message SendEmailRequest { + EmailMessage email = 1; +} + +// Represents a WebSocket packet. +message WebSocketPacket { + string type = 1; + google.protobuf.Value data = 2; + google.protobuf.StringValue error_message = 3; +} + +message PushWebSocketPacketRequest { + string user_id = 1; + WebSocketPacket packet = 2; +} + +message PushWebSocketPacketToUsersRequest { + repeated string user_ids = 1; + WebSocketPacket packet = 2; +} + +message PushWebSocketPacketToDeviceRequest { + string device_id = 1; + WebSocketPacket packet = 2; +} + +message PushWebSocketPacketToDevicesRequest { + repeated string device_ids = 1; + WebSocketPacket packet = 2; +} + +// Represents a push notification. +message PushNotification { + string topic = 1; + string title = 2; + string subtitle = 3; + string body = 4; + map meta = 5; + optional string action_uri = 6; + bool is_silent = 7; + bool is_savable = 8; +} + +message SendPushNotificationRequest { + string device_id = 1; + PushNotification notification = 2; +} + +message SendPushNotificationToDevicesRequest { + repeated string device_ids = 1; + PushNotification notification = 2; +} + +message SendPushNotificationToUserRequest { + string user_id = 1; + PushNotification notification = 2; +} + +message SendPushNotificationToUsersRequest { + repeated string user_ids = 1; + PushNotification notification = 2; +} From e66abe2e0cc2002ca2c4577ee94cbff1c1c057be Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Sun, 13 Jul 2025 01:55:35 +0800 Subject: [PATCH 07/42] :recycle: Mix things up --- .gitignore | 1 + DysonNetwork.Pass/AppDatabase.cs | 4 + .../Services/OidcProviderService.cs | 1 + DysonNetwork.Pass/Auth/Session.cs | 3 +- DysonNetwork.Pass/Developer/CustomApp.cs | 68 +++++++++++ DysonNetwork.Pass/Email/EmailService.cs | 113 ++++++------------ DysonNetwork.Pass/Program.cs | 4 + .../ServiceRegistrationHostedService.cs | 56 +++++++++ DysonNetwork.Pusher/Program.cs | 4 + .../Startup/ServiceCollectionExtensions.cs | 14 +++ .../ServiceRegistrationHostedService.cs | 56 +++++++++ DysonNetwork.Shared/Auth/AuthConstants.cs | 22 ++++ .../DysonNetwork.Shared.csproj | 4 + .../Middleware/AuthMiddleware.cs | 107 +++++++++++++++++ DysonNetwork.Shared/Proto/GrpcClientHelper.cs | 104 ++++++++++++++++ .../Registry/ServiceRegistry.cs | 28 +++++ DysonNetwork.Shared/Registry/Startup.cs | 23 ++++ DysonNetwork.Sphere/appsettings.json | 8 +- DysonNetwork.sln.DotSettings.user | 4 + 19 files changed, 546 insertions(+), 78 deletions(-) create mode 100644 DysonNetwork.Pass/Developer/CustomApp.cs create mode 100644 DysonNetwork.Pass/Startup/ServiceRegistrationHostedService.cs create mode 100644 DysonNetwork.Pusher/Startup/ServiceRegistrationHostedService.cs create mode 100644 DysonNetwork.Shared/Auth/AuthConstants.cs create mode 100644 DysonNetwork.Shared/Middleware/AuthMiddleware.cs create mode 100644 DysonNetwork.Shared/Proto/GrpcClientHelper.cs create mode 100644 DysonNetwork.Shared/Registry/ServiceRegistry.cs create mode 100644 DysonNetwork.Shared/Registry/Startup.cs diff --git a/.gitignore b/.gitignore index 30f23d9..c7242c6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ bin/ obj/ /packages/ +/Certificates/ riderModule.iml /_ReSharper.Caches/ .idea diff --git a/DysonNetwork.Pass/AppDatabase.cs b/DysonNetwork.Pass/AppDatabase.cs index f666e8f..695c3b2 100644 --- a/DysonNetwork.Pass/AppDatabase.cs +++ b/DysonNetwork.Pass/AppDatabase.cs @@ -2,6 +2,7 @@ using System.Linq.Expressions; using System.Reflection; using DysonNetwork.Pass.Account; using DysonNetwork.Pass.Auth; +using DysonNetwork.Pass.Developer; using DysonNetwork.Pass.Permission; using DysonNetwork.Pass.Wallet; using DysonNetwork.Shared.Data; @@ -46,6 +47,9 @@ public class AppDatabase( public DbSet PaymentTransactions { get; set; } public DbSet WalletSubscriptions { get; set; } public DbSet WalletCoupons { get; set; } + + public DbSet CustomApps { get; set; } + public DbSet CustomAppSecrets { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { diff --git a/DysonNetwork.Pass/Auth/OidcProvider/Services/OidcProviderService.cs b/DysonNetwork.Pass/Auth/OidcProvider/Services/OidcProviderService.cs index 0c1d86e..2ddb88d 100644 --- a/DysonNetwork.Pass/Auth/OidcProvider/Services/OidcProviderService.cs +++ b/DysonNetwork.Pass/Auth/OidcProvider/Services/OidcProviderService.cs @@ -5,6 +5,7 @@ using System.Text; using DysonNetwork.Pass.Auth.OidcProvider.Models; using DysonNetwork.Pass.Auth.OidcProvider.Options; using DysonNetwork.Pass.Auth.OidcProvider.Responses; +using DysonNetwork.Pass.Developer; using DysonNetwork.Shared.Cache; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Options; diff --git a/DysonNetwork.Pass/Auth/Session.cs b/DysonNetwork.Pass/Auth/Session.cs index d5f935d..149fb65 100644 --- a/DysonNetwork.Pass/Auth/Session.cs +++ b/DysonNetwork.Pass/Auth/Session.cs @@ -2,6 +2,7 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using System.Text.Json.Serialization; using DysonNetwork.Pass; +using DysonNetwork.Pass.Developer; using DysonNetwork.Shared.Data; using NodaTime; using NodaTime.Serialization.Protobuf; @@ -21,7 +22,7 @@ public class AuthSession : ModelBase public Guid ChallengeId { get; set; } public AuthChallenge Challenge { get; set; } = null!; public Guid? AppId { get; set; } - // public CustomApp? App { get; set; } + public CustomApp? App { get; set; } public Shared.Proto.AuthSession ToProtoValue() => new() { diff --git a/DysonNetwork.Pass/Developer/CustomApp.cs b/DysonNetwork.Pass/Developer/CustomApp.cs new file mode 100644 index 0000000..731d8cc --- /dev/null +++ b/DysonNetwork.Pass/Developer/CustomApp.cs @@ -0,0 +1,68 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Text.Json.Serialization; +using DysonNetwork.Pass.Account; +using DysonNetwork.Shared.Data; +using NodaTime; + +namespace DysonNetwork.Pass.Developer; + +public enum CustomAppStatus +{ + Developing, + Staging, + Production, + Suspended +} + +public class CustomApp : ModelBase, IIdentifiedResource +{ + public Guid Id { get; set; } = Guid.NewGuid(); + [MaxLength(1024)] public string Slug { get; set; } = null!; + [MaxLength(1024)] public string Name { get; set; } = null!; + [MaxLength(4096)] public string? Description { get; set; } + public CustomAppStatus Status { get; set; } = CustomAppStatus.Developing; + + [Column(TypeName = "jsonb")] public CloudFileReferenceObject? Picture { get; set; } + [Column(TypeName = "jsonb")] public CloudFileReferenceObject? Background { get; set; } + + [Column(TypeName = "jsonb")] public VerificationMark? Verification { get; set; } + [Column(TypeName = "jsonb")] public CustomAppOauthConfig? OauthConfig { get; set; } + [Column(TypeName = "jsonb")] public CustomAppLinks? Links { get; set; } + + [JsonIgnore] public ICollection Secrets { get; set; } = new List(); + + // TODO: Publisher + + [NotMapped] public string ResourceIdentifier => "custom-app/" + Id; +} + +public class CustomAppLinks +{ + [MaxLength(8192)] public string? HomePage { get; set; } + [MaxLength(8192)] public string? PrivacyPolicy { get; set; } + [MaxLength(8192)] public string? TermsOfService { get; set; } +} + +public class CustomAppOauthConfig +{ + [MaxLength(1024)] public string? ClientUri { get; set; } + [MaxLength(4096)] public string[] RedirectUris { get; set; } = []; + [MaxLength(4096)] public string[]? PostLogoutRedirectUris { get; set; } + [MaxLength(256)] public string[]? AllowedScopes { get; set; } = ["openid", "profile", "email"]; + [MaxLength(256)] public string[] AllowedGrantTypes { get; set; } = ["authorization_code", "refresh_token"]; + public bool RequirePkce { get; set; } = true; + public bool AllowOfflineAccess { get; set; } = false; +} + +public class CustomAppSecret : ModelBase +{ + public Guid Id { get; set; } = Guid.NewGuid(); + [MaxLength(1024)] public string Secret { get; set; } = null!; + [MaxLength(4096)] public string? Description { get; set; } = null!; + public Instant? ExpiredAt { get; set; } + public bool IsOidc { get; set; } = false; // Indicates if this secret is for OIDC/OAuth + + public Guid AppId { get; set; } + public CustomApp App { get; set; } = null!; +} diff --git a/DysonNetwork.Pass/Email/EmailService.cs b/DysonNetwork.Pass/Email/EmailService.cs index 2051cf4..b8bb428 100644 --- a/DysonNetwork.Pass/Email/EmailService.cs +++ b/DysonNetwork.Pass/Email/EmailService.cs @@ -1,92 +1,56 @@ -using MailKit.Net.Smtp; +using dotnet_etcd; +using DysonNetwork.Shared.Proto; using Microsoft.AspNetCore.Components; -using MimeKit; namespace DysonNetwork.Pass.Email; -public class EmailServiceConfiguration -{ - public string Server { get; set; } = null!; - public int Port { get; set; } - public bool UseSsl { get; set; } - public string Username { get; set; } = null!; - public string Password { get; set; } = null!; - public string FromAddress { get; set; } = null!; - public string FromName { get; set; } = null!; - public string SubjectPrefix { get; set; } = null!; -} - public class EmailService { - private readonly EmailServiceConfiguration _configuration; + private readonly PusherService.PusherServiceClient _client; private readonly RazorViewRenderer _viewRenderer; private readonly ILogger _logger; - public EmailService(IConfiguration configuration, RazorViewRenderer viewRenderer, ILogger logger) + public EmailService( + EtcdClient etcd, + RazorViewRenderer viewRenderer, + IConfiguration configuration, + ILogger logger, + PusherService.PusherServiceClient client + ) { - var cfg = configuration.GetSection("Email").Get(); - _configuration = cfg ?? throw new ArgumentException("Email service was not configured."); + _client = GrpcClientHelper.CreatePusherServiceClient( + etcd, + configuration["Service:CertPath"]!, + configuration["Service:KeyPath"]! + ).GetAwaiter().GetResult(); _viewRenderer = viewRenderer; _logger = logger; + _client = client; } - - public async Task SendEmailAsync(string? recipientName, string recipientEmail, string subject, string textBody) + + public async Task SendEmailAsync( + string? recipientName, + string recipientEmail, + string subject, + string htmlBody + ) { - await SendEmailAsync(recipientName, recipientEmail, subject, textBody, null); + subject = $"[Solarpass] {subject}"; + + await _client.SendEmailAsync( + new SendEmailRequest() + { + Email = new EmailMessage() + { + ToName = recipientName, + ToAddress = recipientEmail, + Subject = subject, + Body = htmlBody + } + } + ); } - public async Task SendEmailAsync(string? recipientName, string recipientEmail, string subject, string textBody, - string? htmlBody) - { - subject = $"[{_configuration.SubjectPrefix}] {subject}"; - - var emailMessage = new MimeMessage(); - emailMessage.From.Add(new MailboxAddress(_configuration.FromName, _configuration.FromAddress)); - emailMessage.To.Add(new MailboxAddress(recipientName, recipientEmail)); - emailMessage.Subject = subject; - - var bodyBuilder = new BodyBuilder - { - TextBody = textBody - }; - - if (!string.IsNullOrEmpty(htmlBody)) - bodyBuilder.HtmlBody = htmlBody; - - emailMessage.Body = bodyBuilder.ToMessageBody(); - - using var client = new SmtpClient(); - await client.ConnectAsync(_configuration.Server, _configuration.Port, _configuration.UseSsl); - await client.AuthenticateAsync(_configuration.Username, _configuration.Password); - await client.SendAsync(emailMessage); - await client.DisconnectAsync(true); - } - - private static string _ConvertHtmlToPlainText(string html) - { - // Remove style tags and their contents - html = System.Text.RegularExpressions.Regex.Replace(html, "]*>.*?", "", - System.Text.RegularExpressions.RegexOptions.Singleline); - - // Replace header tags with text + newlines - html = System.Text.RegularExpressions.Regex.Replace(html, "]*>(.*?)", "$1\n\n", - System.Text.RegularExpressions.RegexOptions.IgnoreCase); - - // Replace line breaks - html = html.Replace("
", "\n").Replace("
", "\n").Replace("
", "\n"); - - // Remove all remaining HTML tags - html = System.Text.RegularExpressions.Regex.Replace(html, "<[^>]+>", ""); - - // Decode HTML entities - html = System.Net.WebUtility.HtmlDecode(html); - - // Remove excess whitespace - html = System.Text.RegularExpressions.Regex.Replace(html, @"\s+", " ").Trim(); - - return html; - } - public async Task SendTemplatedEmailAsync(string? recipientName, string recipientEmail, string subject, TModel model) where TComponent : IComponent @@ -94,8 +58,7 @@ public class EmailService try { var htmlBody = await _viewRenderer.RenderComponentToStringAsync(model); - var fallbackTextBody = _ConvertHtmlToPlainText(htmlBody); - await SendEmailAsync(recipientName, recipientEmail, subject, fallbackTextBody, htmlBody); + await SendEmailAsync(recipientName, recipientEmail, subject, htmlBody); } catch (Exception err) { diff --git a/DysonNetwork.Pass/Program.cs b/DysonNetwork.Pass/Program.cs index e52a3c3..c6b9257 100644 --- a/DysonNetwork.Pass/Program.cs +++ b/DysonNetwork.Pass/Program.cs @@ -1,6 +1,7 @@ using DysonNetwork.Pass; using DysonNetwork.Pass.Account; using DysonNetwork.Pass.Startup; +using DysonNetwork.Shared.Registry; using Microsoft.EntityFrameworkCore; var builder = WebApplication.CreateBuilder(args); @@ -12,6 +13,7 @@ builder.ConfigureAppKestrel(); builder.Services.AddAppMetrics(); // Add application services +builder.Services.AddEtcdService(builder.Configuration); builder.Services.AddAppServices(builder.Configuration); builder.Services.AddAppRateLimiting(); builder.Services.AddAppAuthentication(); @@ -26,6 +28,8 @@ builder.Services.AddAppBusinessServices(builder.Configuration); // Add scheduled jobs builder.Services.AddAppScheduledJobs(); +builder.Services.AddHostedService(); + var app = builder.Build(); // Run database migrations diff --git a/DysonNetwork.Pass/Startup/ServiceRegistrationHostedService.cs b/DysonNetwork.Pass/Startup/ServiceRegistrationHostedService.cs new file mode 100644 index 0000000..7abe7fc --- /dev/null +++ b/DysonNetwork.Pass/Startup/ServiceRegistrationHostedService.cs @@ -0,0 +1,56 @@ +using DysonNetwork.Shared.Registry; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using System.Threading; +using System.Threading.Tasks; + +namespace DysonNetwork.Pass.Startup; + +public class ServiceRegistrationHostedService : IHostedService +{ + private readonly ServiceRegistry _serviceRegistry; + private readonly IConfiguration _configuration; + private readonly ILogger _logger; + + public ServiceRegistrationHostedService( + ServiceRegistry serviceRegistry, + IConfiguration configuration, + ILogger logger) + { + _serviceRegistry = serviceRegistry; + _configuration = configuration; + _logger = logger; + } + + public async Task StartAsync(CancellationToken cancellationToken) + { + var serviceName = "DysonNetwork.Pass"; // Preset service name + var serviceUrl = _configuration["Service:Url"]; + + if (string.IsNullOrEmpty(serviceName) || string.IsNullOrEmpty(serviceUrl)) + { + _logger.LogWarning("Service name or URL not configured. Skipping Etcd registration."); + return; + } + + _logger.LogInformation("Registering service {ServiceName} at {ServiceUrl} with Etcd.", serviceName, serviceUrl); + try + { + await _serviceRegistry.RegisterService(serviceName, serviceUrl); + _logger.LogInformation("Service {ServiceName} registered successfully.", serviceName); + } + catch (Exception ex) + { + _logger.LogError(ex, "Failed to register service {ServiceName} with Etcd.", serviceName); + } + } + + public Task StopAsync(CancellationToken cancellationToken) + { + // The lease will expire automatically if the service stops. + // For explicit unregistration, you would implement it here. + _logger.LogInformation("Service registration hosted service is stopping."); + return Task.CompletedTask; + } +} diff --git a/DysonNetwork.Pusher/Program.cs b/DysonNetwork.Pusher/Program.cs index 744c79d..6f8fc57 100644 --- a/DysonNetwork.Pusher/Program.cs +++ b/DysonNetwork.Pusher/Program.cs @@ -23,6 +23,8 @@ builder.Services.AddAppBusinessServices(); // Add scheduled jobs builder.Services.AddAppScheduledJobs(); +builder.Services.AddHostedService(); + var app = builder.Build(); // Run database migrations @@ -35,6 +37,8 @@ using (var scope = app.Services.CreateScope()) // Configure application middleware pipeline app.ConfigureAppMiddleware(builder.Configuration); +app.UseMiddleware(); + // Configure gRPC app.ConfigureGrpcServices(); diff --git a/DysonNetwork.Pusher/Startup/ServiceCollectionExtensions.cs b/DysonNetwork.Pusher/Startup/ServiceCollectionExtensions.cs index 033a98b..bbcfe8e 100644 --- a/DysonNetwork.Pusher/Startup/ServiceCollectionExtensions.cs +++ b/DysonNetwork.Pusher/Startup/ServiceCollectionExtensions.cs @@ -1,9 +1,11 @@ using System.Text.Json; using System.Threading.RateLimiting; +using dotnet_etcd.interfaces; using DysonNetwork.Pusher.Email; using DysonNetwork.Pusher.Notification; using DysonNetwork.Pusher.Services; using DysonNetwork.Shared.Cache; +using DysonNetwork.Shared.Proto; using Microsoft.AspNetCore.RateLimiting; using Microsoft.OpenApi.Models; using NodaTime; @@ -42,6 +44,18 @@ public static class ServiceCollectionExtensions // Register gRPC services services.AddScoped(); + // Register AuthService.AuthServiceClient for AuthMiddleware + services.AddSingleton(sp => + { + var etcdClient = sp.GetRequiredService(); + var configuration = sp.GetRequiredService(); + var clientCertPath = configuration["ClientCert:Path"]; + var clientKeyPath = configuration["ClientKey:Path"]; + var clientCertPassword = configuration["ClientCert:Password"]; + + return GrpcClientHelper.CreateAuthServiceClient(etcdClient, clientCertPath, clientKeyPath, clientCertPassword); + }); + // Register OIDC services services.AddControllers().AddJsonOptions(options => { diff --git a/DysonNetwork.Pusher/Startup/ServiceRegistrationHostedService.cs b/DysonNetwork.Pusher/Startup/ServiceRegistrationHostedService.cs new file mode 100644 index 0000000..c68d3fb --- /dev/null +++ b/DysonNetwork.Pusher/Startup/ServiceRegistrationHostedService.cs @@ -0,0 +1,56 @@ +using DysonNetwork.Shared.Registry; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using System.Threading; +using System.Threading.Tasks; + +namespace DysonNetwork.Pusher.Startup; + +public class ServiceRegistrationHostedService : IHostedService +{ + private readonly ServiceRegistry _serviceRegistry; + private readonly IConfiguration _configuration; + private readonly ILogger _logger; + + public ServiceRegistrationHostedService( + ServiceRegistry serviceRegistry, + IConfiguration configuration, + ILogger logger) + { + _serviceRegistry = serviceRegistry; + _configuration = configuration; + _logger = logger; + } + + public async Task StartAsync(CancellationToken cancellationToken) + { + var serviceName = "DysonNetwork.Pusher"; // Preset service name + var serviceUrl = _configuration["Service:Url"]; + + if (string.IsNullOrEmpty(serviceUrl)) + { + _logger.LogWarning("Service URL not configured. Skipping Etcd registration."); + return; + } + + _logger.LogInformation("Registering service {ServiceName} at {ServiceUrl} with Etcd.", serviceName, serviceUrl); + try + { + await _serviceRegistry.RegisterService(serviceName, serviceUrl); + _logger.LogInformation("Service {ServiceName} registered successfully.", serviceName); + } + catch (Exception ex) + { + _logger.LogError(ex, "Failed to register service {ServiceName} with Etcd.", serviceName); + } + } + + public Task StopAsync(CancellationToken cancellationToken) + { + // The lease will expire automatically if the service stops. + // For explicit unregistration, you would implement it here. + _logger.LogInformation("Service registration hosted service is stopping."); + return Task.CompletedTask; + } +} diff --git a/DysonNetwork.Shared/Auth/AuthConstants.cs b/DysonNetwork.Shared/Auth/AuthConstants.cs new file mode 100644 index 0000000..8b84521 --- /dev/null +++ b/DysonNetwork.Shared/Auth/AuthConstants.cs @@ -0,0 +1,22 @@ +namespace DysonNetwork.Shared.Auth; + +public static class AuthConstants +{ + public const string SchemeName = "DysonToken"; + public const string TokenQueryParamName = "tk"; + public const string CookieTokenName = "AuthToken"; +} + +public enum TokenType +{ + AuthKey, + ApiKey, + OidcKey, + Unknown +} + +public class TokenInfo +{ + public string Token { get; set; } = string.Empty; + public TokenType Type { get; set; } = TokenType.Unknown; +} diff --git a/DysonNetwork.Shared/DysonNetwork.Shared.csproj b/DysonNetwork.Shared/DysonNetwork.Shared.csproj index 5d993e2..0c82947 100644 --- a/DysonNetwork.Shared/DysonNetwork.Shared.csproj +++ b/DysonNetwork.Shared/DysonNetwork.Shared.csproj @@ -7,6 +7,7 @@ + @@ -17,6 +18,8 @@ runtime; build; native; contentfiles; analyzers; buildtransitive + + @@ -24,6 +27,7 @@ + diff --git a/DysonNetwork.Shared/Middleware/AuthMiddleware.cs b/DysonNetwork.Shared/Middleware/AuthMiddleware.cs new file mode 100644 index 0000000..6356427 --- /dev/null +++ b/DysonNetwork.Shared/Middleware/AuthMiddleware.cs @@ -0,0 +1,107 @@ +using Grpc.Core; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; +using DysonNetwork.Shared.Proto; +using System.Threading.Tasks; +using DysonNetwork.Shared.Auth; + +namespace DysonNetwork.Shared.Middleware; + +public class AuthMiddleware +{ + private readonly RequestDelegate _next; + private readonly ILogger _logger; + + public AuthMiddleware(RequestDelegate next, ILogger logger) + { + _next = next; + _logger = logger; + } + + public async Task InvokeAsync(HttpContext context, AuthService.AuthServiceClient authServiceClient) + { + var tokenInfo = _ExtractToken(context.Request); + + if (tokenInfo == null || string.IsNullOrEmpty(tokenInfo.Token)) + { + await _next(context); + return; + } + + try + { + var authSession = await authServiceClient.AuthenticateAsync(new AuthenticateRequest { Token = tokenInfo.Token }); + context.Items["AuthSession"] = authSession; + context.Items["CurrentTokenType"] = tokenInfo.Type.ToString(); + // Assuming AuthSession contains Account information or can be retrieved + // context.Items["CurrentUser"] = authSession.Account; // You might need to fetch Account separately if not embedded + } + catch (RpcException ex) + { + _logger.LogWarning(ex, "Authentication failed for token: {Token}", tokenInfo.Token); + // Optionally, you can return an unauthorized response here + // context.Response.StatusCode = StatusCodes.Status401Unauthorized; + // return; + } + + await _next(context); + } + + private TokenInfo? _ExtractToken(HttpRequest request) + { + // Check for token in query parameters + if (request.Query.TryGetValue(AuthConstants.TokenQueryParamName, out var queryToken)) + { + return new TokenInfo + { + Token = queryToken.ToString(), + Type = TokenType.AuthKey + }; + } + + // Check for token in Authorization header + var authHeader = request.Headers["Authorization"].ToString(); + if (!string.IsNullOrEmpty(authHeader)) + { + if (authHeader.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase)) + { + var token = authHeader["Bearer ".Length..].Trim(); + var parts = token.Split('.'); + + return new TokenInfo + { + Token = token, + Type = parts.Length == 3 ? TokenType.OidcKey : TokenType.AuthKey + }; + } + else if (authHeader.StartsWith("AtField ", StringComparison.OrdinalIgnoreCase)) + { + return new TokenInfo + { + Token = authHeader["AtField ".Length..].Trim(), + Type = TokenType.AuthKey + }; + } + else if (authHeader.StartsWith("AkField ", StringComparison.OrdinalIgnoreCase)) + { + return new TokenInfo + { + Token = authHeader["AkField ".Length..].Trim(), + Type = TokenType.ApiKey + }; + } + } + + // Check for token in cookies + if (request.Cookies.TryGetValue(AuthConstants.CookieTokenName, out var cookieToken)) + { + return new TokenInfo + { + Token = cookieToken, + Type = cookieToken.Count(c => c == '.') == 2 ? TokenType.OidcKey : TokenType.AuthKey + }; + } + + return null; + } +} diff --git a/DysonNetwork.Shared/Proto/GrpcClientHelper.cs b/DysonNetwork.Shared/Proto/GrpcClientHelper.cs new file mode 100644 index 0000000..f891317 --- /dev/null +++ b/DysonNetwork.Shared/Proto/GrpcClientHelper.cs @@ -0,0 +1,104 @@ +using Grpc.Net.Client; +using System.Security.Cryptography.X509Certificates; +using Grpc.Core; +using dotnet_etcd.interfaces; + +namespace DysonNetwork.Shared.Proto; + +public static class GrpcClientHelper +{ + private static CallInvoker CreateCallInvoker( + string url, + string clientCertPath, + string clientKeyPath, + string? clientCertPassword = null + ) + { + var handler = new HttpClientHandler(); + handler.ClientCertificates.Add( + clientCertPassword is null ? + X509Certificate2.CreateFromPemFile(clientCertPath, clientKeyPath) : + X509Certificate2.CreateFromEncryptedPemFile(clientCertPath, clientCertPassword, clientKeyPath) + ); + return GrpcChannel.ForAddress(url, new GrpcChannelOptions { HttpHandler = handler }).CreateCallInvoker(); + } + + private static async Task GetServiceUrlFromEtcd(IEtcdClient etcdClient, string serviceName) + { + var response = await etcdClient.GetAsync($"/services/{serviceName}"); + if (response.Kvs.Count == 0) + { + throw new InvalidOperationException($"Service '{serviceName}' not found in Etcd."); + } + return response.Kvs[0].Value.ToStringUtf8(); + } + + public static AccountService.AccountServiceClient CreateAccountServiceClient( + string url, + string clientCertPath, + string clientKeyPath, + string? clientCertPassword = null + ) + { + return new AccountService.AccountServiceClient(CreateCallInvoker(url, clientCertPath, clientKeyPath, + clientCertPassword)); + } + + public static async Task CreateAccountServiceClient( + IEtcdClient etcdClient, + string clientCertPath, + string clientKeyPath, + string? clientCertPassword = null + ) + { + var url = await GetServiceUrlFromEtcd(etcdClient, "AccountService"); + return new AccountService.AccountServiceClient(CreateCallInvoker(url, clientCertPath, clientKeyPath, + clientCertPassword)); + } + + public static AuthService.AuthServiceClient CreateAuthServiceClient( + string url, + string clientCertPath, + string clientKeyPath, + string? clientCertPassword = null + ) + { + return new AuthService.AuthServiceClient(CreateCallInvoker(url, clientCertPath, clientKeyPath, + clientCertPassword)); + } + + public static async Task CreateAuthServiceClient( + IEtcdClient etcdClient, + string clientCertPath, + string clientKeyPath, + string? clientCertPassword = null + ) + { + var url = await GetServiceUrlFromEtcd(etcdClient, "AuthService"); + return new AuthService.AuthServiceClient(CreateCallInvoker(url, clientCertPath, clientKeyPath, + clientCertPassword)); + } + + public static PusherService.PusherServiceClient CreatePusherServiceClient( + string url, + string clientCertPath, + string clientKeyPath, + string? clientCertPassword = null + ) + { + return new PusherService.PusherServiceClient(CreateCallInvoker(url, clientCertPath, clientKeyPath, + clientCertPassword)); + } + + public static async Task CreatePusherServiceClient( + IEtcdClient etcdClient, + string clientCertPath, + string clientKeyPath, + string? clientCertPassword = null + ) + { + var url = await GetServiceUrlFromEtcd(etcdClient, "PusherService"); + return new PusherService.PusherServiceClient(CreateCallInvoker(url, clientCertPath, clientKeyPath, + clientCertPassword)); + } +} \ No newline at end of file diff --git a/DysonNetwork.Shared/Registry/ServiceRegistry.cs b/DysonNetwork.Shared/Registry/ServiceRegistry.cs new file mode 100644 index 0000000..8d3e7f4 --- /dev/null +++ b/DysonNetwork.Shared/Registry/ServiceRegistry.cs @@ -0,0 +1,28 @@ +using System.Text; +using dotnet_etcd.interfaces; +using Etcdserverpb; +using Google.Protobuf; + +namespace DysonNetwork.Shared.Registry; + +public class ServiceRegistry(IEtcdClient etcd) +{ + public async Task RegisterService(string serviceName, string serviceUrl, long leaseTtlSeconds = 60) + { + var key = $"/services/{serviceName}"; + var leaseResponse = await etcd.LeaseGrantAsync(new LeaseGrantRequest { TTL = leaseTtlSeconds }); + await etcd.PutAsync(new PutRequest + { + Key = ByteString.CopyFrom(key, Encoding.UTF8), + Value = ByteString.CopyFrom(serviceUrl, Encoding.UTF8), + Lease = leaseResponse.ID + }); + await etcd.LeaseKeepAlive(leaseResponse.ID, CancellationToken.None); + } + + public async Task UnregisterService(string serviceName) + { + var key = $"/services/{serviceName}"; + await etcd.DeleteAsync(key); + } +} \ No newline at end of file diff --git a/DysonNetwork.Shared/Registry/Startup.cs b/DysonNetwork.Shared/Registry/Startup.cs new file mode 100644 index 0000000..cceef2d --- /dev/null +++ b/DysonNetwork.Shared/Registry/Startup.cs @@ -0,0 +1,23 @@ +using dotnet_etcd.DependencyInjection; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace DysonNetwork.Shared.Registry; + +public static class EtcdStartup +{ + public static IServiceCollection AddEtcdService( + this IServiceCollection services, + IConfiguration configuration + ) + { + services.AddEtcdClient(options => + { + options.ConnectionString = configuration.GetConnectionString("Etcd"); + options.UseInsecureChannel = configuration.GetValue("Etcd:Insecure"); + }); + services.AddSingleton(); + + return services; + } +} \ No newline at end of file diff --git a/DysonNetwork.Sphere/appsettings.json b/DysonNetwork.Sphere/appsettings.json index 82c1088..5cb458b 100644 --- a/DysonNetwork.Sphere/appsettings.json +++ b/DysonNetwork.Sphere/appsettings.json @@ -10,7 +10,8 @@ "AllowedHosts": "*", "ConnectionStrings": { "App": "Host=localhost;Port=5432;Database=dyson_network;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60", - "FastRetrieve": "localhost:6379" + "FastRetrieve": "localhost:6379", + "Etcd": "localhost:2379" }, "Authentication": { "Schemes": { @@ -125,5 +126,8 @@ "KnownProxies": [ "127.0.0.1", "::1" - ] + ], + "Etcd": { + "Insecure": true + } } diff --git a/DysonNetwork.sln.DotSettings.user b/DysonNetwork.sln.DotSettings.user index 41ec08d..95dd6c2 100644 --- a/DysonNetwork.sln.DotSettings.user +++ b/DysonNetwork.sln.DotSettings.user @@ -10,6 +10,8 @@ ForceIncluded ForceIncluded ForceIncluded + ForceIncluded + ForceIncluded ForceIncluded ForceIncluded ForceIncluded @@ -39,8 +41,10 @@ ForceIncluded ForceIncluded ForceIncluded + ForceIncluded ForceIncluded ForceIncluded + ForceIncluded ForceIncluded ForceIncluded ForceIncluded From afdbde951cd53c0a76820391fb0084c3a274ad45 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Sun, 13 Jul 2025 18:36:51 +0800 Subject: [PATCH 08/42] :sparkles: Shared auth scheme --- DysonNetwork.Drive/AppDatabase.cs | 179 ++++++ DysonNetwork.Drive/Dockerfile | 23 + DysonNetwork.Drive/DysonNetwork.Drive.csproj | 66 +++ DysonNetwork.Drive/Program.cs | 45 ++ .../Properties/launchSettings.json | 23 + .../Startup/ApplicationBuilderExtensions.cs | 28 + .../Startup/KestrelConfiguration.cs | 17 + .../Startup/ScheduledJobsConfiguration.cs | 22 + .../Startup/ServiceCollectionExtensions.cs | 130 ++++ DysonNetwork.Drive/Storage/CloudFile.cs | 131 +++++ .../Storage/CloudFileUnusedRecyclingJob.cs | 93 +++ DysonNetwork.Drive/Storage/FileController.cs | 144 +++++ .../Storage/FileExpirationJob.cs | 66 +++ .../Storage/FileReferenceService.cs | 434 ++++++++++++++ DysonNetwork.Drive/Storage/FileService.cs | 555 ++++++++++++++++++ DysonNetwork.Drive/Storage/TusService.cs | 79 +++ DysonNetwork.Drive/appsettings.json | 129 ++++ DysonNetwork.Pass/Auth/AuthServiceGrpc.cs | 46 +- DysonNetwork.Pass/DysonNetwork.Pass.csproj | 52 +- .../Permission/PermissionServiceGrpc.cs | 96 +++ DysonNetwork.Pass/Program.cs | 2 +- .../DysonNetwork.Pusher.csproj | 1 + DysonNetwork.Pusher/Program.cs | 9 +- .../Startup/KestrelConfiguration.cs | 2 +- DysonNetwork.Shared/Auth/AuthScheme.cs | 154 +++++ DysonNetwork.Shared/Auth/Startup.cs | 35 ++ .../DysonNetwork.Shared.csproj | 19 +- .../Middleware/AuthMiddleware.cs | 107 ---- DysonNetwork.Shared/Proto/auth.proto | 118 +++- .../Registry/RegistryHostedService.cs | 45 ++ DysonNetwork.Shared/Registry/Startup.cs | 5 +- .../DysonNetwork.Sphere.csproj | 20 +- DysonNetwork.sln | 6 + DysonNetwork.sln.DotSettings.user | 2 + 34 files changed, 2704 insertions(+), 179 deletions(-) create mode 100644 DysonNetwork.Drive/AppDatabase.cs create mode 100644 DysonNetwork.Drive/Dockerfile create mode 100644 DysonNetwork.Drive/DysonNetwork.Drive.csproj create mode 100644 DysonNetwork.Drive/Program.cs create mode 100644 DysonNetwork.Drive/Properties/launchSettings.json create mode 100644 DysonNetwork.Drive/Startup/ApplicationBuilderExtensions.cs create mode 100644 DysonNetwork.Drive/Startup/KestrelConfiguration.cs create mode 100644 DysonNetwork.Drive/Startup/ScheduledJobsConfiguration.cs create mode 100644 DysonNetwork.Drive/Startup/ServiceCollectionExtensions.cs create mode 100644 DysonNetwork.Drive/Storage/CloudFile.cs create mode 100644 DysonNetwork.Drive/Storage/CloudFileUnusedRecyclingJob.cs create mode 100644 DysonNetwork.Drive/Storage/FileController.cs create mode 100644 DysonNetwork.Drive/Storage/FileExpirationJob.cs create mode 100644 DysonNetwork.Drive/Storage/FileReferenceService.cs create mode 100644 DysonNetwork.Drive/Storage/FileService.cs create mode 100644 DysonNetwork.Drive/Storage/TusService.cs create mode 100644 DysonNetwork.Drive/appsettings.json create mode 100644 DysonNetwork.Pass/Permission/PermissionServiceGrpc.cs create mode 100644 DysonNetwork.Shared/Auth/AuthScheme.cs create mode 100644 DysonNetwork.Shared/Auth/Startup.cs delete mode 100644 DysonNetwork.Shared/Middleware/AuthMiddleware.cs create mode 100644 DysonNetwork.Shared/Registry/RegistryHostedService.cs diff --git a/DysonNetwork.Drive/AppDatabase.cs b/DysonNetwork.Drive/AppDatabase.cs new file mode 100644 index 0000000..74a7e1c --- /dev/null +++ b/DysonNetwork.Drive/AppDatabase.cs @@ -0,0 +1,179 @@ +using System.Linq.Expressions; +using System.Reflection; +using DysonNetwork.Drive.Storage; +using DysonNetwork.Shared.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Design; +using Microsoft.EntityFrameworkCore.Query; +using NodaTime; +using Quartz; + +namespace DysonNetwork.Drive; + +public class AppDatabase( + DbContextOptions options, + IConfiguration configuration +) : DbContext(options) +{ + public DbSet Files { get; set; } = null!; + public DbSet FileReferences { get; set; } = null!; + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + optionsBuilder.UseNpgsql( + configuration.GetConnectionString("App"), + opt => opt + .ConfigureDataSource(optSource => optSource.EnableDynamicJson()) + .UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery) + .UseNetTopologySuite() + .UseNodaTime() + ).UseSnakeCaseNamingConvention(); + + base.OnConfiguring(optionsBuilder); + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + // Automatically apply soft-delete filter to all entities inheriting BaseModel + foreach (var entityType in modelBuilder.Model.GetEntityTypes()) + { + if (!typeof(ModelBase).IsAssignableFrom(entityType.ClrType)) continue; + var method = typeof(AppDatabase) + .GetMethod(nameof(SetSoftDeleteFilter), + BindingFlags.NonPublic | BindingFlags.Static)! + .MakeGenericMethod(entityType.ClrType); + + method.Invoke(null, [modelBuilder]); + } + } + + private static void SetSoftDeleteFilter(ModelBuilder modelBuilder) + where TEntity : ModelBase + { + modelBuilder.Entity().HasQueryFilter(e => e.DeletedAt == null); + } + + public override async Task SaveChangesAsync(CancellationToken cancellationToken = default) + { + var now = SystemClock.Instance.GetCurrentInstant(); + + foreach (var entry in ChangeTracker.Entries()) + { + switch (entry.State) + { + case EntityState.Added: + entry.Entity.CreatedAt = now; + entry.Entity.UpdatedAt = now; + break; + case EntityState.Modified: + entry.Entity.UpdatedAt = now; + break; + case EntityState.Deleted: + entry.State = EntityState.Modified; + entry.Entity.DeletedAt = now; + break; + case EntityState.Detached: + case EntityState.Unchanged: + default: + break; + } + } + + return await base.SaveChangesAsync(cancellationToken); + } +} + +public class AppDatabaseRecyclingJob(AppDatabase db, ILogger logger) : IJob +{ + public async Task Execute(IJobExecutionContext context) + { + var now = SystemClock.Instance.GetCurrentInstant(); + + logger.LogInformation("Deleting soft-deleted records..."); + + var threshold = now - Duration.FromDays(7); + + var entityTypes = db.Model.GetEntityTypes() + .Where(t => typeof(ModelBase).IsAssignableFrom(t.ClrType) && t.ClrType != typeof(ModelBase)) + .Select(t => t.ClrType); + + foreach (var entityType in entityTypes) + { + var set = (IQueryable)db.GetType().GetMethod(nameof(DbContext.Set), Type.EmptyTypes)! + .MakeGenericMethod(entityType).Invoke(db, null)!; + var parameter = Expression.Parameter(entityType, "e"); + var property = Expression.Property(parameter, nameof(ModelBase.DeletedAt)); + var condition = Expression.LessThan(property, Expression.Constant(threshold, typeof(Instant?))); + var notNull = Expression.NotEqual(property, Expression.Constant(null, typeof(Instant?))); + var finalCondition = Expression.AndAlso(notNull, condition); + var lambda = Expression.Lambda(finalCondition, parameter); + + var queryable = set.Provider.CreateQuery( + Expression.Call( + typeof(Queryable), + "Where", + [entityType], + set.Expression, + Expression.Quote(lambda) + ) + ); + + var toListAsync = typeof(EntityFrameworkQueryableExtensions) + .GetMethod(nameof(EntityFrameworkQueryableExtensions.ToListAsync))! + .MakeGenericMethod(entityType); + + var items = await (dynamic)toListAsync.Invoke(null, [queryable, CancellationToken.None])!; + db.RemoveRange(items); + } + + await db.SaveChangesAsync(); + } +} + +public class AppDatabaseFactory : IDesignTimeDbContextFactory +{ + public AppDatabase CreateDbContext(string[] args) + { + var configuration = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json") + .Build(); + + var optionsBuilder = new DbContextOptionsBuilder(); + return new AppDatabase(optionsBuilder.Options, configuration); + } +} + +public static class OptionalQueryExtensions +{ + public static IQueryable If( + this IQueryable source, + bool condition, + Func, IQueryable> transform + ) + { + return condition ? transform(source) : source; + } + + public static IQueryable If( + this IIncludableQueryable source, + bool condition, + Func, IQueryable> transform + ) + where T : class + { + return condition ? transform(source) : source; + } + + public static IQueryable If( + this IIncludableQueryable> source, + bool condition, + Func>, IQueryable> transform + ) + where T : class + { + return condition ? transform(source) : source; + } +} \ No newline at end of file diff --git a/DysonNetwork.Drive/Dockerfile b/DysonNetwork.Drive/Dockerfile new file mode 100644 index 0000000..dd92d67 --- /dev/null +++ b/DysonNetwork.Drive/Dockerfile @@ -0,0 +1,23 @@ +FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base +USER $APP_UID +WORKDIR /app +EXPOSE 8080 +EXPOSE 8081 + +FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build +ARG BUILD_CONFIGURATION=Release +WORKDIR /src +COPY ["DysonNetwork.Drive/DysonNetwork.Drive.csproj", "DysonNetwork.Drive/"] +RUN dotnet restore "DysonNetwork.Drive/DysonNetwork.Drive.csproj" +COPY . . +WORKDIR "/src/DysonNetwork.Drive" +RUN dotnet build "./DysonNetwork.Drive.csproj" -c $BUILD_CONFIGURATION -o /app/build + +FROM build AS publish +ARG BUILD_CONFIGURATION=Release +RUN dotnet publish "./DysonNetwork.Drive.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false + +FROM base AS final +WORKDIR /app +COPY --from=publish /app/publish . +ENTRYPOINT ["dotnet", "DysonNetwork.Drive.dll"] diff --git a/DysonNetwork.Drive/DysonNetwork.Drive.csproj b/DysonNetwork.Drive/DysonNetwork.Drive.csproj new file mode 100644 index 0000000..199eb2d --- /dev/null +++ b/DysonNetwork.Drive/DysonNetwork.Drive.csproj @@ -0,0 +1,66 @@ + + + + net9.0 + enable + enable + Linux + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + .dockerignore + + + + + + + + diff --git a/DysonNetwork.Drive/Program.cs b/DysonNetwork.Drive/Program.cs new file mode 100644 index 0000000..e701c37 --- /dev/null +++ b/DysonNetwork.Drive/Program.cs @@ -0,0 +1,45 @@ +using DysonNetwork.Drive; +using DysonNetwork.Drive.Startup; +using DysonNetwork.Pusher.Startup; +using DysonNetwork.Shared.Auth; +using DysonNetwork.Shared.Registry; +using Microsoft.EntityFrameworkCore; + +var builder = WebApplication.CreateBuilder(args); + +// Configure Kestrel and server options +builder.ConfigureAppKestrel(); + +// Add application services +builder.Services.AddRegistryService(builder.Configuration); +builder.Services.AddAppServices(builder.Configuration); +builder.Services.AddAppRateLimiting(); +builder.Services.AddAppAuthentication(); +builder.Services.AddAppSwagger(); +builder.Services.AddDysonAuth(builder.Configuration); + +// Add flush handlers and websocket handlers +builder.Services.AddAppFlushHandlers(); + +// Add business services +builder.Services.AddAppBusinessServices(); + +// Add scheduled jobs +builder.Services.AddAppScheduledJobs(); + +var app = builder.Build(); + +// Run database migrations +using (var scope = app.Services.CreateScope()) +{ + var db = scope.ServiceProvider.GetRequiredService(); + await db.Database.MigrateAsync(); +} + +// Configure application middleware pipeline +app.ConfigureAppMiddleware(builder.Configuration); + +// Configure gRPC +app.ConfigureGrpcServices(); + +app.Run(); \ No newline at end of file diff --git a/DysonNetwork.Drive/Properties/launchSettings.json b/DysonNetwork.Drive/Properties/launchSettings.json new file mode 100644 index 0000000..d4fe22b --- /dev/null +++ b/DysonNetwork.Drive/Properties/launchSettings.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "http://localhost:5090", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "https://localhost:7092;http://localhost:5090", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/DysonNetwork.Drive/Startup/ApplicationBuilderExtensions.cs b/DysonNetwork.Drive/Startup/ApplicationBuilderExtensions.cs new file mode 100644 index 0000000..7504fbe --- /dev/null +++ b/DysonNetwork.Drive/Startup/ApplicationBuilderExtensions.cs @@ -0,0 +1,28 @@ +namespace DysonNetwork.Drive.Startup; + +public static class ApplicationBuilderExtensions +{ + public static WebApplication ConfigureAppMiddleware(this WebApplication app, IConfiguration configuration) + { + // Configure the HTTP request pipeline. + if (app.Environment.IsDevelopment()) + { + app.UseSwagger(); + app.UseSwaggerUI(); + } + + app.UseHttpsRedirection(); + app.UseAuthorization(); + app.MapControllers(); + + return app; + } + + public static WebApplication ConfigureGrpcServices(this WebApplication app) + { + // Map your gRPC services here + // Example: app.MapGrpcService(); + + return app; + } +} diff --git a/DysonNetwork.Drive/Startup/KestrelConfiguration.cs b/DysonNetwork.Drive/Startup/KestrelConfiguration.cs new file mode 100644 index 0000000..f35e4dd --- /dev/null +++ b/DysonNetwork.Drive/Startup/KestrelConfiguration.cs @@ -0,0 +1,17 @@ +namespace DysonNetwork.Pusher.Startup; + +public static class KestrelConfiguration +{ + public static WebApplicationBuilder ConfigureAppKestrel(this WebApplicationBuilder builder) + { + builder.Host.UseContentRoot(Directory.GetCurrentDirectory()); + builder.WebHost.ConfigureKestrel(options => + { + options.Limits.MaxRequestBodySize = 50 * 1024 * 1024; + options.Limits.KeepAliveTimeout = TimeSpan.FromMinutes(2); + options.Limits.RequestHeadersTimeout = TimeSpan.FromSeconds(30); + }); + + return builder; + } +} diff --git a/DysonNetwork.Drive/Startup/ScheduledJobsConfiguration.cs b/DysonNetwork.Drive/Startup/ScheduledJobsConfiguration.cs new file mode 100644 index 0000000..e4fc9eb --- /dev/null +++ b/DysonNetwork.Drive/Startup/ScheduledJobsConfiguration.cs @@ -0,0 +1,22 @@ +using Quartz; + +namespace DysonNetwork.Drive.Startup; + +public static class ScheduledJobsConfiguration +{ + public static IServiceCollection AddAppScheduledJobs(this IServiceCollection services) + { + services.AddQuartz(q => + { + var appDatabaseRecyclingJob = new JobKey("AppDatabaseRecycling"); + q.AddJob(opts => opts.WithIdentity(appDatabaseRecyclingJob)); + q.AddTrigger(opts => opts + .ForJob(appDatabaseRecyclingJob) + .WithIdentity("AppDatabaseRecyclingTrigger") + .WithCronSchedule("0 0 0 * * ?")); + }); + services.AddQuartzHostedService(q => q.WaitForJobsToComplete = true); + + return services; + } +} diff --git a/DysonNetwork.Drive/Startup/ServiceCollectionExtensions.cs b/DysonNetwork.Drive/Startup/ServiceCollectionExtensions.cs new file mode 100644 index 0000000..a9b4ae1 --- /dev/null +++ b/DysonNetwork.Drive/Startup/ServiceCollectionExtensions.cs @@ -0,0 +1,130 @@ +using System.Text.Json; +using System.Threading.RateLimiting; +using dotnet_etcd.interfaces; +using DysonNetwork.Shared.Cache; +using Microsoft.AspNetCore.RateLimiting; +using Microsoft.OpenApi.Models; +using NodaTime; +using NodaTime.Serialization.SystemTextJson; +using StackExchange.Redis; +using DysonNetwork.Shared.Proto; + +namespace DysonNetwork.Drive.Startup; + +public static class ServiceCollectionExtensions +{ + public static IServiceCollection AddAppServices(this IServiceCollection services, IConfiguration configuration) + { + services.AddDbContext(); // Assuming you'll have an AppDatabase + services.AddSingleton(_ => + { + var connection = configuration.GetConnectionString("FastRetrieve")!; + return ConnectionMultiplexer.Connect(connection); + }); + services.AddSingleton(SystemClock.Instance); + services.AddHttpContextAccessor(); + services.AddSingleton(); // Uncomment if you have CacheServiceRedis + + services.AddHttpClient(); + + // Register gRPC services + services.AddGrpc(options => + { + options.EnableDetailedErrors = true; // Will be adjusted in Program.cs + options.MaxReceiveMessageSize = 16 * 1024 * 1024; // 16MB + options.MaxSendMessageSize = 16 * 1024 * 1024; // 16MB + }); + + // Register gRPC reflection for service discovery + services.AddGrpc(); + + services.AddControllers().AddJsonOptions(options => + { + options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower; + options.JsonSerializerOptions.DictionaryKeyPolicy = JsonNamingPolicy.SnakeCaseLower; + + options.JsonSerializerOptions.ConfigureForNodaTime(DateTimeZoneProviders.Tzdb); + }); + + return services; + } + + public static IServiceCollection AddAppRateLimiting(this IServiceCollection services) + { + services.AddRateLimiter(o => o.AddFixedWindowLimiter(policyName: "fixed", opts => + { + opts.Window = TimeSpan.FromMinutes(1); + opts.PermitLimit = 120; + opts.QueueLimit = 2; + opts.QueueProcessingOrder = QueueProcessingOrder.OldestFirst; + })); + + return services; + } + + public static IServiceCollection AddAppAuthentication(this IServiceCollection services) + { + services.AddCors(); + services.AddAuthorization(); + + return services; + } + + public static IServiceCollection AddAppFlushHandlers(this IServiceCollection services) + { + services.AddSingleton(); + + return services; + } + + public static IServiceCollection AddAppSwagger(this IServiceCollection services) + { + services.AddEndpointsApiExplorer(); + services.AddSwaggerGen(options => + { + options.SwaggerDoc("v1", new OpenApiInfo + { + Version = "v1", + Title = "DysonNetwork.Drive API", + Description = "DysonNetwork Drive Service", + TermsOfService = new Uri("https://example.com/terms"), // Update with actual terms + License = new OpenApiLicense + { + Name = "APGLv3", // Update with actual license + Url = new Uri("https://www.gnu.org/licenses/agpl-3.0.html") + } + }); + options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme + { + In = ParameterLocation.Header, + Description = "Please enter a valid token", + Name = "Authorization", + Type = SecuritySchemeType.Http, + BearerFormat = "JWT", + Scheme = "Bearer" + }); + options.AddSecurityRequirement(new OpenApiSecurityRequirement + { + { + new OpenApiSecurityScheme + { + Reference = new OpenApiReference + { + Type = ReferenceType.SecurityScheme, + Id = "Bearer" + } + }, + [] + } + }); + }); + + return services; + } + + public static IServiceCollection AddAppBusinessServices(this IServiceCollection services) + { + // Add your business services here + return services; + } +} diff --git a/DysonNetwork.Drive/Storage/CloudFile.cs b/DysonNetwork.Drive/Storage/CloudFile.cs new file mode 100644 index 0000000..c5c6f35 --- /dev/null +++ b/DysonNetwork.Drive/Storage/CloudFile.cs @@ -0,0 +1,131 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Text.Json.Serialization; +using DysonNetwork.Shared.Data; +using DysonNetwork.Shared.Proto; +using NodaTime; + +namespace DysonNetwork.Drive.Storage; + +public class RemoteStorageConfig +{ + public string Id { get; set; } = string.Empty; + public string Label { get; set; } = string.Empty; + public string Region { get; set; } = string.Empty; + public string Bucket { get; set; } = string.Empty; + public string Endpoint { get; set; } = string.Empty; + public string SecretId { get; set; } = string.Empty; + public string SecretKey { get; set; } = string.Empty; + public bool EnableSigned { get; set; } + public bool EnableSsl { get; set; } + public string? ImageProxy { get; set; } + public string? AccessProxy { get; set; } +} + +/// +/// The class that used in jsonb columns which referenced the cloud file. +/// The aim of this class is to store some properties that won't change to a file to reduce the database load. +/// +public class CloudFileReferenceObject : ModelBase, ICloudFile +{ + public string Id { get; set; } = null!; + public string Name { get; set; } = string.Empty; + public Dictionary? FileMeta { get; set; } = null!; + public Dictionary? UserMeta { get; set; } = null!; + public string? MimeType { get; set; } + public string? Hash { get; set; } + public long Size { get; set; } + public bool HasCompression { get; set; } = false; +} + +public class CloudFile : ModelBase, ICloudFile, IIdentifiedResource +{ + /// The id generated by TuS, basically just UUID remove the dash lines + [MaxLength(32)] + public string Id { get; set; } = Guid.NewGuid().ToString(); + + [MaxLength(1024)] public string Name { get; set; } = string.Empty; + [MaxLength(4096)] public string? Description { get; set; } + [Column(TypeName = "jsonb")] public Dictionary? FileMeta { get; set; } = null!; + [Column(TypeName = "jsonb")] public Dictionary? UserMeta { get; set; } = null!; + [Column(TypeName = "jsonb")] public List? SensitiveMarks { get; set; } = []; + [MaxLength(256)] public string? MimeType { get; set; } + [MaxLength(256)] public string? Hash { get; set; } + public long Size { get; set; } + public Instant? UploadedAt { get; set; } + [MaxLength(128)] public string? UploadedTo { get; set; } + public bool HasCompression { get; set; } = false; + + /// + /// The field is set to true if the recycling job plans to delete the file. + /// Due to the unstable of the recycling job, this doesn't really delete the file until a human verifies it. + /// + public bool IsMarkedRecycle { get; set; } = false; + + /// The object name which stored remotely, + /// multiple cloud file may have same storage id to indicate they are the same file + /// + /// If the storage id was null and the uploaded at is not null, means it is an embedding file, + /// The embedding file means the file is store on another site, + /// or it is a webpage (based on mimetype) + [MaxLength(32)] + public string? StorageId { get; set; } + + /// This field should be null when the storage id is filled + /// Indicates the off-site accessible url of the file + [MaxLength(4096)] + public string? StorageUrl { get; set; } + + public Guid AccountId { get; set; } + + public CloudFileReferenceObject ToReferenceObject() + { + return new CloudFileReferenceObject + { + CreatedAt = CreatedAt, + UpdatedAt = UpdatedAt, + DeletedAt = DeletedAt, + Id = Id, + Name = Name, + FileMeta = FileMeta, + UserMeta = UserMeta, + MimeType = MimeType, + Hash = Hash, + Size = Size, + HasCompression = HasCompression + }; + } + + public string ResourceIdentifier => $"file/{Id}"; +} + +public enum ContentSensitiveMark +{ + Language, + SexualContent, + Violence, + Profanity, + HateSpeech, + Racism, + AdultContent, + DrugAbuse, + AlcoholAbuse, + Gambling, + SelfHarm, + ChildAbuse, + Other +} + +public class CloudFileReference : ModelBase +{ + public Guid Id { get; set; } = Guid.NewGuid(); + [MaxLength(32)] public string FileId { get; set; } = null!; + public CloudFile File { get; set; } = null!; + [MaxLength(1024)] public string Usage { get; set; } = null!; + [MaxLength(1024)] public string ResourceId { get; set; } = null!; + + /// + /// Optional expiration date for the file reference + /// + public Instant? ExpiredAt { get; set; } +} \ No newline at end of file diff --git a/DysonNetwork.Drive/Storage/CloudFileUnusedRecyclingJob.cs b/DysonNetwork.Drive/Storage/CloudFileUnusedRecyclingJob.cs new file mode 100644 index 0000000..98d4cdb --- /dev/null +++ b/DysonNetwork.Drive/Storage/CloudFileUnusedRecyclingJob.cs @@ -0,0 +1,93 @@ +using Microsoft.EntityFrameworkCore; +using NodaTime; +using Quartz; + +namespace DysonNetwork.Drive.Storage; + +public class CloudFileUnusedRecyclingJob( + AppDatabase db, + FileReferenceService fileRefService, + ILogger logger +) + : IJob +{ + public async Task Execute(IJobExecutionContext context) + { + logger.LogInformation("Marking unused cloud files..."); + + var now = SystemClock.Instance.GetCurrentInstant(); + const int batchSize = 1000; // Process larger batches for efficiency + var processedCount = 0; + var markedCount = 0; + var totalFiles = await db.Files.Where(f => !f.IsMarkedRecycle).CountAsync(); + + logger.LogInformation("Found {TotalFiles} files to check for unused status", totalFiles); + + // Define a timestamp to limit the age of files we're processing in this run + // This spreads the processing across multiple job runs for very large databases + var ageThreshold = now - Duration.FromDays(30); // Process files up to 90 days old in this run + + // Instead of loading all files at once, use pagination + var hasMoreFiles = true; + string? lastProcessedId = null; + + while (hasMoreFiles) + { + // Query for the next batch of files using keyset pagination + var filesQuery = db.Files + .Where(f => !f.IsMarkedRecycle) + .Where(f => f.CreatedAt <= ageThreshold); // Only process older files first + + if (lastProcessedId != null) + { + filesQuery = filesQuery.Where(f => string.Compare(f.Id, lastProcessedId) > 0); + } + + var fileBatch = await filesQuery + .OrderBy(f => f.Id) // Ensure consistent ordering for pagination + .Take(batchSize) + .Select(f => f.Id) + .ToListAsync(); + + if (fileBatch.Count == 0) + { + hasMoreFiles = false; + continue; + } + + processedCount += fileBatch.Count; + lastProcessedId = fileBatch.Last(); + + // Get all relevant file references for this batch + var fileReferences = await fileRefService.GetReferencesAsync(fileBatch); + + // Filter to find files that have no references or all expired references + var filesToMark = fileBatch.Where(fileId => + !fileReferences.TryGetValue(fileId, out var references) || + references.Count == 0 || + references.All(r => r.ExpiredAt.HasValue && r.ExpiredAt.Value <= now) + ).ToList(); + + if (filesToMark.Count > 0) + { + // Use a bulk update for better performance - mark all qualifying files at once + var updateCount = await db.Files + .Where(f => filesToMark.Contains(f.Id)) + .ExecuteUpdateAsync(setter => setter + .SetProperty(f => f.IsMarkedRecycle, true)); + + markedCount += updateCount; + } + + // Log progress periodically + if (processedCount % 10000 == 0 || !hasMoreFiles) + { + logger.LogInformation( + "Progress: processed {ProcessedCount}/{TotalFiles} files, marked {MarkedCount} for recycling", + processedCount, totalFiles, markedCount); + } + } + + logger.LogInformation("Completed marking {MarkedCount} files for recycling", markedCount); + } +} \ No newline at end of file diff --git a/DysonNetwork.Drive/Storage/FileController.cs b/DysonNetwork.Drive/Storage/FileController.cs new file mode 100644 index 0000000..e8b4f49 --- /dev/null +++ b/DysonNetwork.Drive/Storage/FileController.cs @@ -0,0 +1,144 @@ +using DysonNetwork.Shared.Proto; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Minio.DataModel.Args; + +namespace DysonNetwork.Drive.Storage; + +[ApiController] +[Route("/api/files")] +public class FileController( + AppDatabase db, + FileService fs, + IConfiguration configuration, + IWebHostEnvironment env +) : ControllerBase +{ + [HttpGet("{id}")] + public async Task OpenFile( + string id, + [FromQuery] bool download = false, + [FromQuery] bool original = false, + [FromQuery] string? overrideMimeType = null + ) + { + // Support the file extension for client side data recognize + string? fileExtension = null; + if (id.Contains('.')) + { + var splitId = id.Split('.'); + id = splitId.First(); + fileExtension = splitId.Last(); + } + + var file = await fs.GetFileAsync(id); + if (file is null) return NotFound(); + + if (!string.IsNullOrWhiteSpace(file.StorageUrl)) return Redirect(file.StorageUrl); + + if (file.UploadedTo is null) + { + var tusStorePath = configuration.GetValue("Tus:StorePath")!; + var filePath = Path.Combine(env.ContentRootPath, tusStorePath, file.Id); + if (!System.IO.File.Exists(filePath)) return new NotFoundResult(); + return PhysicalFile(filePath, file.MimeType ?? "application/octet-stream", file.Name); + } + + var dest = fs.GetRemoteStorageConfig(file.UploadedTo); + var fileName = string.IsNullOrWhiteSpace(file.StorageId) ? file.Id : file.StorageId; + + if (!original && file.HasCompression) + fileName += ".compressed"; + + if (dest.ImageProxy is not null && (file.MimeType?.StartsWith("image/") ?? false)) + { + var proxyUrl = dest.ImageProxy; + var baseUri = new Uri(proxyUrl.EndsWith('/') ? proxyUrl : $"{proxyUrl}/"); + var fullUri = new Uri(baseUri, fileName); + return Redirect(fullUri.ToString()); + } + + if (dest.AccessProxy is not null) + { + var proxyUrl = dest.AccessProxy; + var baseUri = new Uri(proxyUrl.EndsWith('/') ? proxyUrl : $"{proxyUrl}/"); + var fullUri = new Uri(baseUri, fileName); + return Redirect(fullUri.ToString()); + } + + if (dest.EnableSigned) + { + var client = fs.CreateMinioClient(dest); + if (client is null) + return BadRequest( + "Failed to configure client for remote destination, file got an invalid storage remote."); + + var headers = new Dictionary(); + if (fileExtension is not null) + { + if (MimeTypes.TryGetMimeType(fileExtension, out var mimeType)) + headers.Add("Response-Content-Type", mimeType); + } + else if (overrideMimeType is not null) + { + headers.Add("Response-Content-Type", overrideMimeType); + } + else if (file.MimeType is not null && !file.MimeType!.EndsWith("unknown")) + { + headers.Add("Response-Content-Type", file.MimeType); + } + + if (download) + { + headers.Add("Response-Content-Disposition", $"attachment; filename=\"{file.Name}\""); + } + + var bucket = dest.Bucket; + var openUrl = await client.PresignedGetObjectAsync( + new PresignedGetObjectArgs() + .WithBucket(bucket) + .WithObject(fileName) + .WithExpiry(3600) + .WithHeaders(headers) + ); + + return Redirect(openUrl); + } + + // Fallback redirect to the S3 endpoint (public read) + var protocol = dest.EnableSsl ? "https" : "http"; + // Use the path bucket lookup mode + return Redirect($"{protocol}://{dest.Endpoint}/{dest.Bucket}/{fileName}"); + } + + [HttpGet("{id}/info")] + public async Task> GetFileInfo(string id) + { + var file = await db.Files.FindAsync(id); + if (file is null) return NotFound(); + + return file; + } + + [Authorize] + [HttpDelete("{id}")] + public async Task DeleteFile(string id) + { + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); + var userId = Guid.Parse(currentUser.Id); + + var file = await db.Files + .Where(e => e.Id == id) + .Where(e => e.AccountId == userId) + .FirstOrDefaultAsync(); + if (file is null) return NotFound(); + + await fs.DeleteFileAsync(file); + + db.Files.Remove(file); + await db.SaveChangesAsync(); + + return NoContent(); + } +} \ No newline at end of file diff --git a/DysonNetwork.Drive/Storage/FileExpirationJob.cs b/DysonNetwork.Drive/Storage/FileExpirationJob.cs new file mode 100644 index 0000000..13fde3d --- /dev/null +++ b/DysonNetwork.Drive/Storage/FileExpirationJob.cs @@ -0,0 +1,66 @@ +using Microsoft.EntityFrameworkCore; +using NodaTime; +using Quartz; + +namespace DysonNetwork.Drive.Storage; + +/// +/// Job responsible for cleaning up expired file references +/// +public class FileExpirationJob(AppDatabase db, FileService fileService, ILogger logger) : IJob +{ + public async Task Execute(IJobExecutionContext context) + { + var now = SystemClock.Instance.GetCurrentInstant(); + logger.LogInformation("Running file reference expiration job at {now}", now); + + // Find all expired references + var expiredReferences = await db.FileReferences + .Where(r => r.ExpiredAt < now && r.ExpiredAt != null) + .ToListAsync(); + + if (!expiredReferences.Any()) + { + logger.LogInformation("No expired file references found"); + return; + } + + logger.LogInformation("Found {count} expired file references", expiredReferences.Count); + + // Get unique file IDs + var fileIds = expiredReferences.Select(r => r.FileId).Distinct().ToList(); + var filesAndReferenceCount = new Dictionary(); + + // Delete expired references + db.FileReferences.RemoveRange(expiredReferences); + await db.SaveChangesAsync(); + + // Check remaining references for each file + foreach (var fileId in fileIds) + { + var remainingReferences = await db.FileReferences + .Where(r => r.FileId == fileId) + .CountAsync(); + + filesAndReferenceCount[fileId] = remainingReferences; + + // If no references remain, delete the file + if (remainingReferences == 0) + { + var file = await db.Files.FirstOrDefaultAsync(f => f.Id == fileId); + if (file != null) + { + logger.LogInformation("Deleting file {fileId} as all references have expired", fileId); + await fileService.DeleteFileAsync(file); + } + } + else + { + // Just purge the cache + await fileService._PurgeCacheAsync(fileId); + } + } + + logger.LogInformation("Completed file reference expiration job"); + } +} diff --git a/DysonNetwork.Drive/Storage/FileReferenceService.cs b/DysonNetwork.Drive/Storage/FileReferenceService.cs new file mode 100644 index 0000000..94a704a --- /dev/null +++ b/DysonNetwork.Drive/Storage/FileReferenceService.cs @@ -0,0 +1,434 @@ +using DysonNetwork.Shared.Cache; +using Microsoft.EntityFrameworkCore; +using NodaTime; + +namespace DysonNetwork.Drive.Storage; + +public class FileReferenceService(AppDatabase db, FileService fileService, ICacheService cache) +{ + private const string CacheKeyPrefix = "fileref:"; + private static readonly TimeSpan CacheDuration = TimeSpan.FromMinutes(15); + + /// + /// Creates a new reference to a file for a specific resource + /// + /// The ID of the file to reference + /// The usage context (e.g., "avatar", "post-attachment") + /// The ID of the resource using the file + /// Optional expiration time for the file + /// Optional duration after which the file expires (alternative to expiredAt) + /// The created file reference + public async Task CreateReferenceAsync( + string fileId, + string usage, + string resourceId, + Instant? expiredAt = null, + Duration? duration = null) + { + // Calculate expiration time if needed + var finalExpiration = expiredAt; + if (duration.HasValue) + finalExpiration = SystemClock.Instance.GetCurrentInstant() + duration.Value; + + var reference = new CloudFileReference + { + FileId = fileId, + Usage = usage, + ResourceId = resourceId, + ExpiredAt = finalExpiration + }; + + db.FileReferences.Add(reference); + + await db.SaveChangesAsync(); + await fileService._PurgeCacheAsync(fileId); + + return reference; + } + + /// + /// Gets all references to a file + /// + /// The ID of the file + /// A list of all references to the file + public async Task> GetReferencesAsync(string fileId) + { + var cacheKey = $"{CacheKeyPrefix}list:{fileId}"; + + var cachedReferences = await cache.GetAsync>(cacheKey); + if (cachedReferences is not null) + return cachedReferences; + + var references = await db.FileReferences + .Where(r => r.FileId == fileId) + .ToListAsync(); + + await cache.SetAsync(cacheKey, references, CacheDuration); + + return references; + } + + public async Task>> GetReferencesAsync(IEnumerable fileId) + { + var references = await db.FileReferences + .Where(r => fileId.Contains(r.FileId)) + .GroupBy(r => r.FileId) + .ToDictionaryAsync(r => r.Key, r => r.ToList()); + return references; + } + + /// + /// Gets the number of references to a file + /// + /// The ID of the file + /// The number of references to the file + public async Task GetReferenceCountAsync(string fileId) + { + var cacheKey = $"{CacheKeyPrefix}count:{fileId}"; + + var cachedCount = await cache.GetAsync(cacheKey); + if (cachedCount.HasValue) + return cachedCount.Value; + + var count = await db.FileReferences + .Where(r => r.FileId == fileId) + .CountAsync(); + + await cache.SetAsync(cacheKey, count, CacheDuration); + + return count; + } + + /// + /// Gets all references for a specific resource + /// + /// The ID of the resource + /// A list of file references associated with the resource + public async Task> GetResourceReferencesAsync(string resourceId) + { + var cacheKey = $"{CacheKeyPrefix}resource:{resourceId}"; + + var cachedReferences = await cache.GetAsync>(cacheKey); + if (cachedReferences is not null) + return cachedReferences; + + var references = await db.FileReferences + .Where(r => r.ResourceId == resourceId) + .ToListAsync(); + + await cache.SetAsync(cacheKey, references, CacheDuration); + + return references; + } + + /// + /// Gets all file references for a specific usage context + /// + /// The usage context + /// A list of file references with the specified usage + public async Task> GetUsageReferencesAsync(string usage) + { + return await db.FileReferences + .Where(r => r.Usage == usage) + .ToListAsync(); + } + + /// + /// Deletes references for a specific resource + /// + /// The ID of the resource + /// The number of deleted references + public async Task DeleteResourceReferencesAsync(string resourceId) + { + var references = await db.FileReferences + .Where(r => r.ResourceId == resourceId) + .ToListAsync(); + + var fileIds = references.Select(r => r.FileId).Distinct().ToList(); + + db.FileReferences.RemoveRange(references); + var deletedCount = await db.SaveChangesAsync(); + + // Purge caches + var tasks = fileIds.Select(fileService._PurgeCacheAsync).ToList(); + tasks.Add(PurgeCacheForResourceAsync(resourceId)); + await Task.WhenAll(tasks); + + return deletedCount; + } + + /// + /// Deletes references for a specific resource and usage + /// + /// The ID of the resource + /// The usage context + /// The number of deleted references + public async Task DeleteResourceReferencesAsync(string resourceId, string usage) + { + var references = await db.FileReferences + .Where(r => r.ResourceId == resourceId && r.Usage == usage) + .ToListAsync(); + + if (!references.Any()) + { + return 0; + } + + var fileIds = references.Select(r => r.FileId).Distinct().ToList(); + + db.FileReferences.RemoveRange(references); + var deletedCount = await db.SaveChangesAsync(); + + // Purge caches + var tasks = fileIds.Select(fileService._PurgeCacheAsync).ToList(); + tasks.Add(PurgeCacheForResourceAsync(resourceId)); + await Task.WhenAll(tasks); + + return deletedCount; + } + + /// + /// Deletes a specific file reference + /// + /// The ID of the reference to delete + /// True if the reference was deleted, false otherwise + public async Task DeleteReferenceAsync(Guid referenceId) + { + var reference = await db.FileReferences + .FirstOrDefaultAsync(r => r.Id == referenceId); + + if (reference == null) + return false; + + db.FileReferences.Remove(reference); + await db.SaveChangesAsync(); + + // Purge caches + await fileService._PurgeCacheAsync(reference.FileId); + await PurgeCacheForResourceAsync(reference.ResourceId); + await PurgeCacheForFileAsync(reference.FileId); + + return true; + } + + /// + /// Updates the files referenced by a resource + /// + /// The ID of the resource + /// The new list of file IDs + /// The usage context + /// Optional expiration time for newly added files + /// Optional duration after which newly added files expire + /// A list of the updated file references + public async Task> UpdateResourceFilesAsync( + string resourceId, + IEnumerable? newFileIds, + string usage, + Instant? expiredAt = null, + Duration? duration = null) + { + if (newFileIds == null) + return new List(); + + var existingReferences = await db.FileReferences + .Where(r => r.ResourceId == resourceId && r.Usage == usage) + .ToListAsync(); + + var existingFileIds = existingReferences.Select(r => r.FileId).ToHashSet(); + var newFileIdsList = newFileIds.ToList(); + var newFileIdsSet = newFileIdsList.ToHashSet(); + + // Files to remove + var toRemove = existingReferences + .Where(r => !newFileIdsSet.Contains(r.FileId)) + .ToList(); + + // Files to add + var toAdd = newFileIdsList + .Where(id => !existingFileIds.Contains(id)) + .Select(id => new CloudFileReference + { + FileId = id, + Usage = usage, + ResourceId = resourceId + }) + .ToList(); + + // Apply changes + if (toRemove.Any()) + db.FileReferences.RemoveRange(toRemove); + + if (toAdd.Any()) + db.FileReferences.AddRange(toAdd); + + await db.SaveChangesAsync(); + + // Update expiration for newly added references if specified + if ((expiredAt.HasValue || duration.HasValue) && toAdd.Any()) + { + var finalExpiration = expiredAt; + if (duration.HasValue) + { + finalExpiration = SystemClock.Instance.GetCurrentInstant() + duration.Value; + } + + // Update newly added references with the expiration time + var referenceIds = await db.FileReferences + .Where(r => toAdd.Select(a => a.FileId).Contains(r.FileId) && + r.ResourceId == resourceId && + r.Usage == usage) + .Select(r => r.Id) + .ToListAsync(); + + await db.FileReferences + .Where(r => referenceIds.Contains(r.Id)) + .ExecuteUpdateAsync(setter => setter.SetProperty( + r => r.ExpiredAt, + _ => finalExpiration + )); + } + + // Purge caches + var allFileIds = existingFileIds.Union(newFileIdsSet).ToList(); + var tasks = allFileIds.Select(fileService._PurgeCacheAsync).ToList(); + tasks.Add(PurgeCacheForResourceAsync(resourceId)); + await Task.WhenAll(tasks); + + // Return updated references + return await db.FileReferences + .Where(r => r.ResourceId == resourceId && r.Usage == usage) + .ToListAsync(); + } + + /// + /// Gets all files referenced by a resource + /// + /// The ID of the resource + /// Optional filter by usage context + /// A list of files referenced by the resource + public async Task> GetResourceFilesAsync(string resourceId, string? usage = null) + { + var query = db.FileReferences.Where(r => r.ResourceId == resourceId); + + if (usage != null) + query = query.Where(r => r.Usage == usage); + + var references = await query.ToListAsync(); + var fileIds = references.Select(r => r.FileId).ToList(); + + return await db.Files + .Where(f => fileIds.Contains(f.Id)) + .ToListAsync(); + } + + /// + /// Purges all caches related to a resource + /// + private async Task PurgeCacheForResourceAsync(string resourceId) + { + var cacheKey = $"{CacheKeyPrefix}resource:{resourceId}"; + await cache.RemoveAsync(cacheKey); + } + + /// + /// Purges all caches related to a file + /// + private async Task PurgeCacheForFileAsync(string fileId) + { + var cacheKeys = new[] + { + $"{CacheKeyPrefix}list:{fileId}", + $"{CacheKeyPrefix}count:{fileId}" + }; + + var tasks = cacheKeys.Select(cache.RemoveAsync); + await Task.WhenAll(tasks); + } + + /// + /// Updates the expiration time for a file reference + /// + /// The ID of the reference + /// The new expiration time, or null to remove expiration + /// True if the reference was found and updated, false otherwise + public async Task SetReferenceExpirationAsync(Guid referenceId, Instant? expiredAt) + { + var reference = await db.FileReferences + .FirstOrDefaultAsync(r => r.Id == referenceId); + + if (reference == null) + return false; + + reference.ExpiredAt = expiredAt; + await db.SaveChangesAsync(); + + await PurgeCacheForFileAsync(reference.FileId); + await PurgeCacheForResourceAsync(reference.ResourceId); + + return true; + } + + /// + /// Updates the expiration time for all references to a file + /// + /// The ID of the file + /// The new expiration time, or null to remove expiration + /// The number of references updated + public async Task SetFileReferencesExpirationAsync(string fileId, Instant? expiredAt) + { + var rowsAffected = await db.FileReferences + .Where(r => r.FileId == fileId) + .ExecuteUpdateAsync(setter => setter.SetProperty( + r => r.ExpiredAt, + _ => expiredAt + )); + + if (rowsAffected > 0) + { + await fileService._PurgeCacheAsync(fileId); + await PurgeCacheForFileAsync(fileId); + } + + return rowsAffected; + } + + /// + /// Get all file references for a specific resource and usage type + /// + /// The resource ID + /// The usage type + /// List of file references + public async Task> GetResourceReferencesAsync(string resourceId, string usageType) + { + return await db.FileReferences + .Where(r => r.ResourceId == resourceId && r.Usage == usageType) + .ToListAsync(); + } + + /// + /// Check if a file has any references + /// + /// The file ID to check + /// True if the file has references, false otherwise + public async Task HasFileReferencesAsync(string fileId) + { + return await db.FileReferences.AnyAsync(r => r.FileId == fileId); + } + + /// + /// Updates the expiration time for a file reference using a duration from now + /// + /// The ID of the reference + /// The duration after which the reference expires, or null to remove expiration + /// True if the reference was found and updated, false otherwise + public async Task SetReferenceExpirationDurationAsync(Guid referenceId, Duration? duration) + { + Instant? expiredAt = null; + if (duration.HasValue) + { + expiredAt = SystemClock.Instance.GetCurrentInstant() + duration.Value; + } + + return await SetReferenceExpirationAsync(referenceId, expiredAt); + } +} diff --git a/DysonNetwork.Drive/Storage/FileService.cs b/DysonNetwork.Drive/Storage/FileService.cs new file mode 100644 index 0000000..7387de0 --- /dev/null +++ b/DysonNetwork.Drive/Storage/FileService.cs @@ -0,0 +1,555 @@ +using System.Globalization; +using FFMpegCore; +using System.Security.Cryptography; +using DysonNetwork.Shared.Cache; +using DysonNetwork.Shared.Proto; +using Microsoft.EntityFrameworkCore; +using Minio; +using Minio.DataModel.Args; +using NetVips; +using NodaTime; +using tusdotnet.Stores; + +namespace DysonNetwork.Drive.Storage; + +public class FileService( + AppDatabase db, + IConfiguration configuration, + TusDiskStore store, + ILogger logger, + IServiceScopeFactory scopeFactory, + ICacheService cache +) +{ + private const string CacheKeyPrefix = "file:"; + private static readonly TimeSpan CacheDuration = TimeSpan.FromMinutes(15); + + /// + /// The api for getting file meta with cache, + /// the best use case is for accessing the file data. + /// + /// This function won't load uploader's information, only keep minimal file meta + /// + /// The id of the cloud file requested + /// The minimal file meta + public async Task GetFileAsync(string fileId) + { + var cacheKey = $"{CacheKeyPrefix}{fileId}"; + + var cachedFile = await cache.GetAsync(cacheKey); + if (cachedFile is not null) + return cachedFile; + + var file = await db.Files + .Where(f => f.Id == fileId) + .FirstOrDefaultAsync(); + + if (file != null) + await cache.SetAsync(cacheKey, file, CacheDuration); + + return file; + } + + private static readonly string TempFilePrefix = "dyn-cloudfile"; + + private static readonly string[] AnimatedImageTypes = + ["image/gif", "image/apng", "image/webp", "image/avif"]; + + // The analysis file method no longer will remove the GPS EXIF data + // It should be handled on the client side, and for some specific cases it should be keep + public async Task ProcessNewFileAsync( + Account account, + string fileId, + Stream stream, + string fileName, + string? contentType + ) + { + var result = new List<(string filePath, string suffix)>(); + + var ogFilePath = Path.GetFullPath(Path.Join(configuration.GetValue("Tus:StorePath"), fileId)); + var fileSize = stream.Length; + var hash = await HashFileAsync(stream, fileSize: fileSize); + contentType ??= !fileName.Contains('.') ? "application/octet-stream" : MimeTypes.GetMimeType(fileName); + + var file = new CloudFile + { + Id = fileId, + Name = fileName, + MimeType = contentType, + Size = fileSize, + Hash = hash, + AccountId = Guid.Parse(account.Id) + }; + + var existingFile = await db.Files.FirstOrDefaultAsync(f => f.Hash == hash); + file.StorageId = existingFile is not null ? existingFile.StorageId : file.Id; + + if (existingFile is not null) + { + file.FileMeta = existingFile.FileMeta; + file.HasCompression = existingFile.HasCompression; + file.SensitiveMarks = existingFile.SensitiveMarks; + + db.Files.Add(file); + await db.SaveChangesAsync(); + return file; + } + + switch (contentType.Split('/')[0]) + { + case "image": + var blurhash = + BlurHashSharp.SkiaSharp.BlurHashEncoder.Encode(xComponent: 3, yComponent: 3, filename: ogFilePath); + + // Rewind stream + stream.Position = 0; + + // Use NetVips for the rest + using (var vipsImage = NetVips.Image.NewFromStream(stream)) + { + var width = vipsImage.Width; + var height = vipsImage.Height; + var format = vipsImage.Get("vips-loader") ?? "unknown"; + + // Try to get orientation from exif data + var orientation = 1; + var meta = new Dictionary + { + ["blur"] = blurhash, + ["format"] = format, + ["width"] = width, + ["height"] = height, + ["orientation"] = orientation, + }; + Dictionary exif = []; + + foreach (var field in vipsImage.GetFields()) + { + var value = vipsImage.Get(field); + + // Skip GPS-related EXIF fields to remove location data + if (IsIgnoredField(field)) + continue; + + if (field.StartsWith("exif-")) exif[field.Replace("exif-", "")] = value; + else meta[field] = value; + + if (field == "orientation") orientation = (int)value; + } + + if (orientation is 6 or 8) + (width, height) = (height, width); + + var aspectRatio = height != 0 ? (double)width / height : 0; + + meta["exif"] = exif; + meta["ratio"] = aspectRatio; + file.FileMeta = meta; + } + + break; + case "video": + case "audio": + try + { + var mediaInfo = await FFProbe.AnalyseAsync(ogFilePath); + file.FileMeta = new Dictionary + { + ["duration"] = mediaInfo.Duration.TotalSeconds, + ["format_name"] = mediaInfo.Format.FormatName, + ["format_long_name"] = mediaInfo.Format.FormatLongName, + ["start_time"] = mediaInfo.Format.StartTime.ToString(), + ["bit_rate"] = mediaInfo.Format.BitRate.ToString(CultureInfo.InvariantCulture), + ["tags"] = mediaInfo.Format.Tags ?? [], + ["chapters"] = mediaInfo.Chapters, + }; + if (mediaInfo.PrimaryVideoStream is not null) + file.FileMeta["ratio"] = + mediaInfo.PrimaryVideoStream.Width / mediaInfo.PrimaryVideoStream.Height; + } + catch (Exception ex) + { + logger.LogError("File analyzed failed, unable collect video / audio information: {Message}", + ex.Message); + } + + break; + } + + db.Files.Add(file); + await db.SaveChangesAsync(); + + _ = Task.Run(async () => + { + using var scope = scopeFactory.CreateScope(); + var nfs = scope.ServiceProvider.GetRequiredService(); + + try + { + logger.LogInformation("Processed file {fileId}, now trying optimizing if possible...", fileId); + + if (contentType.Split('/')[0] == "image") + { + // Skip compression for animated image types + var animatedMimeTypes = AnimatedImageTypes; + if (Enumerable.Contains(animatedMimeTypes, contentType)) + { + logger.LogInformation( + "File {fileId} is an animated image (MIME: {mime}), skipping WebP conversion.", fileId, + contentType + ); + var tempFilePath = Path.Join(Path.GetTempPath(), $"{TempFilePrefix}#{file.Id}"); + result.Add((tempFilePath, string.Empty)); + return; + } + + file.MimeType = "image/webp"; + + using var vipsImage = Image.NewFromFile(ogFilePath); + var imagePath = Path.Join(Path.GetTempPath(), $"{TempFilePrefix}#{file.Id}"); + vipsImage.Autorot().WriteToFile(imagePath + ".webp", + new VOption { { "lossless", true }, { "strip", true } }); + result.Add((imagePath + ".webp", string.Empty)); + + if (vipsImage.Width * vipsImage.Height >= 1024 * 1024) + { + var scale = 1024.0 / Math.Max(vipsImage.Width, vipsImage.Height); + var imageCompressedPath = + Path.Join(Path.GetTempPath(), $"{TempFilePrefix}#{file.Id}-compressed"); + + // Create and save image within the same synchronous block to avoid disposal issues + using var compressedImage = vipsImage.Resize(scale); + compressedImage.Autorot().WriteToFile(imageCompressedPath + ".webp", + new VOption { { "Q", 80 }, { "strip", true } }); + + result.Add((imageCompressedPath + ".webp", ".compressed")); + file.HasCompression = true; + } + } + else + { + // No extra process for video, add it to the upload queue. + result.Add((ogFilePath, string.Empty)); + } + + logger.LogInformation("Optimized file {fileId}, now uploading...", fileId); + + if (result.Count > 0) + { + List> tasks = []; + tasks.AddRange(result.Select(item => + nfs.UploadFileToRemoteAsync(file, item.filePath, null, item.suffix, true)) + ); + + await Task.WhenAll(tasks); + file = await tasks.First(); + } + else + { + file = await nfs.UploadFileToRemoteAsync(file, stream, null); + } + + logger.LogInformation("Uploaded file {fileId} done!", fileId); + + var scopedDb = scope.ServiceProvider.GetRequiredService(); + await scopedDb.Files.Where(f => f.Id == file.Id).ExecuteUpdateAsync(setter => setter + .SetProperty(f => f.UploadedAt, file.UploadedAt) + .SetProperty(f => f.UploadedTo, file.UploadedTo) + .SetProperty(f => f.MimeType, file.MimeType) + .SetProperty(f => f.HasCompression, file.HasCompression) + ); + } + catch (Exception err) + { + logger.LogError(err, "Failed to process {fileId}", fileId); + } + + await stream.DisposeAsync(); + await store.DeleteFileAsync(file.Id, CancellationToken.None); + await nfs._PurgeCacheAsync(file.Id); + }); + + return file; + } + + private static async Task HashFileAsync(Stream stream, int chunkSize = 1024 * 1024, long? fileSize = null) + { + fileSize ??= stream.Length; + if (fileSize > chunkSize * 1024 * 5) + return await HashFastApproximateAsync(stream, chunkSize); + + using var md5 = MD5.Create(); + var hashBytes = await md5.ComputeHashAsync(stream); + return Convert.ToHexString(hashBytes).ToLowerInvariant(); + } + + private static async Task HashFastApproximateAsync(Stream stream, int chunkSize = 1024 * 1024) + { + // Scale the chunk size to kB level + chunkSize *= 1024; + + using var md5 = MD5.Create(); + + var buffer = new byte[chunkSize * 2]; + var fileLength = stream.Length; + + var bytesRead = await stream.ReadAsync(buffer.AsMemory(0, chunkSize)); + + if (fileLength > chunkSize) + { + stream.Seek(-chunkSize, SeekOrigin.End); + bytesRead += await stream.ReadAsync(buffer.AsMemory(chunkSize, chunkSize)); + } + + var hash = md5.ComputeHash(buffer, 0, bytesRead); + return Convert.ToHexString(hash).ToLowerInvariant(); + } + + public async Task UploadFileToRemoteAsync(CloudFile file, string filePath, string? targetRemote, + string? suffix = null, bool selfDestruct = false) + { + var fileStream = File.OpenRead(filePath); + var result = await UploadFileToRemoteAsync(file, fileStream, targetRemote, suffix); + if (selfDestruct) File.Delete(filePath); + return result; + } + + public async Task UploadFileToRemoteAsync(CloudFile file, Stream stream, string? targetRemote, + string? suffix = null) + { + if (file.UploadedAt.HasValue) return file; + + file.UploadedTo = targetRemote ?? configuration.GetValue("Storage:PreferredRemote")!; + + var dest = GetRemoteStorageConfig(file.UploadedTo); + var client = CreateMinioClient(dest); + if (client is null) + throw new InvalidOperationException( + $"Failed to configure client for remote destination '{file.UploadedTo}'" + ); + + var bucket = dest.Bucket; + var contentType = file.MimeType ?? "application/octet-stream"; + + await client.PutObjectAsync(new PutObjectArgs() + .WithBucket(bucket) + .WithObject(string.IsNullOrWhiteSpace(suffix) ? file.Id : file.Id + suffix) + .WithStreamData(stream) // Fix this disposed + .WithObjectSize(stream.Length) + .WithContentType(contentType) + ); + + file.UploadedAt = Instant.FromDateTimeUtc(DateTime.UtcNow); + return file; + } + + public async Task DeleteFileAsync(CloudFile file) + { + await DeleteFileDataAsync(file); + + db.Remove(file); + await db.SaveChangesAsync(); + await _PurgeCacheAsync(file.Id); + } + + public async Task DeleteFileDataAsync(CloudFile file) + { + if (file.StorageId is null) return; + if (file.UploadedTo is null) return; + + // Check if any other file with the same storage ID is referenced + var otherFilesWithSameStorageId = await db.Files + .Where(f => f.StorageId == file.StorageId && f.Id != file.Id) + .Select(f => f.Id) + .ToListAsync(); + + // Check if any of these files are referenced + var anyReferenced = false; + if (otherFilesWithSameStorageId.Any()) + { + anyReferenced = await db.FileReferences + .Where(r => otherFilesWithSameStorageId.Contains(r.FileId)) + .AnyAsync(); + } + + // If any other file with the same storage ID is referenced, don't delete the actual file data + if (anyReferenced) return; + + var dest = GetRemoteStorageConfig(file.UploadedTo); + var client = CreateMinioClient(dest); + if (client is null) + throw new InvalidOperationException( + $"Failed to configure client for remote destination '{file.UploadedTo}'" + ); + + var bucket = dest.Bucket; + var objectId = file.StorageId ?? file.Id; // Use StorageId if available, otherwise fall back to Id + + await client.RemoveObjectAsync( + new RemoveObjectArgs().WithBucket(bucket).WithObject(objectId) + ); + + if (file.HasCompression) + { + // Also remove the compressed version if it exists + try + { + await client.RemoveObjectAsync( + new RemoveObjectArgs().WithBucket(bucket).WithObject(objectId + ".compressed") + ); + } + catch + { + // Ignore errors when deleting compressed version + logger.LogWarning("Failed to delete compressed version of file {fileId}", file.Id); + } + } + } + + public RemoteStorageConfig GetRemoteStorageConfig(string destination) + { + var destinations = configuration.GetSection("Storage:Remote").Get>()!; + var dest = destinations.FirstOrDefault(d => d.Id == destination); + if (dest is null) throw new InvalidOperationException($"Remote destination '{destination}' not found"); + return dest; + } + + public IMinioClient? CreateMinioClient(RemoteStorageConfig dest) + { + var client = new MinioClient() + .WithEndpoint(dest.Endpoint) + .WithRegion(dest.Region) + .WithCredentials(dest.SecretId, dest.SecretKey); + if (dest.EnableSsl) client = client.WithSSL(); + + return client.Build(); + } + + // Helper method to purge the cache for a specific file + // Made internal to allow FileReferenceService to use it + internal async Task _PurgeCacheAsync(string fileId) + { + var cacheKey = $"{CacheKeyPrefix}{fileId}"; + await cache.RemoveAsync(cacheKey); + } + + // Helper method to purge cache for multiple files + internal async Task _PurgeCacheRangeAsync(IEnumerable fileIds) + { + var tasks = fileIds.Select(_PurgeCacheAsync); + await Task.WhenAll(tasks); + } + + public async Task> LoadFromReference(List references) + { + var cachedFiles = new Dictionary(); + var uncachedIds = new List(); + + // Check cache first + foreach (var reference in references) + { + var cacheKey = $"{CacheKeyPrefix}{reference.Id}"; + var cachedFile = await cache.GetAsync(cacheKey); + + if (cachedFile != null) + { + cachedFiles[reference.Id] = cachedFile; + } + else + { + uncachedIds.Add(reference.Id); + } + } + + // Load uncached files from database + if (uncachedIds.Count > 0) + { + var dbFiles = await db.Files + .Where(f => uncachedIds.Contains(f.Id)) + .ToListAsync(); + + // Add to cache + foreach (var file in dbFiles) + { + var cacheKey = $"{CacheKeyPrefix}{file.Id}"; + await cache.SetAsync(cacheKey, file, CacheDuration); + cachedFiles[file.Id] = file; + } + } + + // Preserve original order + return references + .Select(r => cachedFiles.GetValueOrDefault(r.Id)) + .Where(f => f != null) + .ToList(); + } + + /// + /// Gets the number of references to a file based on CloudFileReference records + /// + /// The ID of the file + /// The number of references to the file + public async Task GetReferenceCountAsync(string fileId) + { + return await db.FileReferences + .Where(r => r.FileId == fileId) + .CountAsync(); + } + + /// + /// Checks if a file is referenced by any resource + /// + /// The ID of the file to check + /// True if the file is referenced, false otherwise + public async Task IsReferencedAsync(string fileId) + { + return await db.FileReferences + .Where(r => r.FileId == fileId) + .AnyAsync(); + } + + /// + /// Checks if an EXIF field contains GPS location data + /// + /// The EXIF field name + /// True if the field contains GPS data, false otherwise + private static bool IsGpsExifField(string fieldName) + { + // Common GPS EXIF field names + var gpsFields = new[] + { + "gps-latitude", + "gps-longitude", + "gps-altitude", + "gps-latitude-ref", + "gps-longitude-ref", + "gps-altitude-ref", + "gps-timestamp", + "gps-datestamp", + "gps-speed", + "gps-speed-ref", + "gps-track", + "gps-track-ref", + "gps-img-direction", + "gps-img-direction-ref", + "gps-dest-latitude", + "gps-dest-longitude", + "gps-dest-latitude-ref", + "gps-dest-longitude-ref", + "gps-processing-method", + "gps-area-information" + }; + + return gpsFields.Any(gpsField => + fieldName.Equals(gpsField, StringComparison.OrdinalIgnoreCase) || + fieldName.StartsWith("gps", StringComparison.OrdinalIgnoreCase)); + } + + private static bool IsIgnoredField(string fieldName) + { + if (IsGpsExifField(fieldName)) return true; + if (fieldName.EndsWith("-data")) return true; + return false; + } +} \ No newline at end of file diff --git a/DysonNetwork.Drive/Storage/TusService.cs b/DysonNetwork.Drive/Storage/TusService.cs new file mode 100644 index 0000000..8f66936 --- /dev/null +++ b/DysonNetwork.Drive/Storage/TusService.cs @@ -0,0 +1,79 @@ +using System.Net; +using System.Text; +using System.Text.Json; +using DysonNetwork.Shared.Proto; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Options; +using tusdotnet.Interfaces; +using tusdotnet.Models; +using tusdotnet.Models.Configuration; + +namespace DysonNetwork.Drive.Storage; + +public abstract class TusService +{ + public static DefaultTusConfiguration BuildConfiguration(ITusStore store) => new() + { + Store = store, + Events = new Events + { + OnAuthorizeAsync = async eventContext => + { + if (eventContext.Intent == IntentType.DeleteFile) + { + eventContext.FailRequest( + HttpStatusCode.BadRequest, + "Deleting files from this endpoint was disabled, please refer to the Dyson Network File API." + ); + return; + } + + var httpContext = eventContext.HttpContext; + if (httpContext.Items["CurrentUser"] is not Account user) + { + eventContext.FailRequest(HttpStatusCode.Unauthorized); + return; + } + + if (!user.IsSuperuser) + { + using var scope = httpContext.RequestServices.CreateScope(); + var pm = scope.ServiceProvider.GetRequiredService(); + var allowed = await pm.HasPermissionAsync(new HasPermissionRequest + { Actor = $"user:{user.Id}", Area = "global", Key = "files.create" }); + if (!allowed.HasPermission) + eventContext.FailRequest(HttpStatusCode.Forbidden); + } + }, + OnFileCompleteAsync = async eventContext => + { + using var scope = eventContext.HttpContext.RequestServices.CreateScope(); + var services = scope.ServiceProvider; + + var httpContext = eventContext.HttpContext; + if (httpContext.Items["CurrentUser"] is not Account user) return; + + var file = await eventContext.GetFileAsync(); + var metadata = await file.GetMetadataAsync(eventContext.CancellationToken); + var fileName = metadata.TryGetValue("filename", out var fn) + ? fn.GetString(Encoding.UTF8) + : "uploaded_file"; + var contentType = metadata.TryGetValue("content-type", out var ct) ? ct.GetString(Encoding.UTF8) : null; + + var fileStream = await file.GetContentAsync(eventContext.CancellationToken); + + var fileService = services.GetRequiredService(); + var info = await fileService.ProcessNewFileAsync(user, file.Id, fileStream, fileName, contentType); + + using var finalScope = eventContext.HttpContext.RequestServices.CreateScope(); + var jsonOptions = finalScope.ServiceProvider.GetRequiredService>().Value + .JsonSerializerOptions; + var infoJson = JsonSerializer.Serialize(info, jsonOptions); + eventContext.HttpContext.Response.Headers.Append("X-FileInfo", infoJson); + + // Dispose the stream after all processing is complete + await fileStream.DisposeAsync(); + } + } + }; +} \ No newline at end of file diff --git a/DysonNetwork.Drive/appsettings.json b/DysonNetwork.Drive/appsettings.json new file mode 100644 index 0000000..82c1088 --- /dev/null +++ b/DysonNetwork.Drive/appsettings.json @@ -0,0 +1,129 @@ +{ + "Debug": true, + "BaseUrl": "http://localhost:5071", + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*", + "ConnectionStrings": { + "App": "Host=localhost;Port=5432;Database=dyson_network;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60", + "FastRetrieve": "localhost:6379" + }, + "Authentication": { + "Schemes": { + "Bearer": { + "ValidAudiences": [ + "http://localhost:5071", + "https://localhost:7099" + ], + "ValidIssuer": "solar-network" + } + } + }, + "AuthToken": { + "PublicKeyPath": "Keys/PublicKey.pem", + "PrivateKeyPath": "Keys/PrivateKey.pem" + }, + "OidcProvider": { + "IssuerUri": "https://nt.solian.app", + "PublicKeyPath": "Keys/PublicKey.pem", + "PrivateKeyPath": "Keys/PrivateKey.pem", + "AccessTokenLifetime": "01:00:00", + "RefreshTokenLifetime": "30.00:00:00", + "AuthorizationCodeLifetime": "00:30:00", + "RequireHttpsMetadata": true + }, + "Tus": { + "StorePath": "Uploads" + }, + "Storage": { + "PreferredRemote": "minio", + "Remote": [ + { + "Id": "minio", + "Label": "Minio", + "Region": "auto", + "Bucket": "solar-network-development", + "Endpoint": "localhost:9000", + "SecretId": "littlesheep", + "SecretKey": "password", + "EnabledSigned": true, + "EnableSsl": false + }, + { + "Id": "cloudflare", + "Label": "Cloudflare R2", + "Region": "auto", + "Bucket": "solar-network", + "Endpoint": "0a70a6d1b7128888c823359d0008f4e1.r2.cloudflarestorage.com", + "SecretId": "8ff5d06c7b1639829d60bc6838a542e6", + "SecretKey": "fd58158c5201be16d1872c9209d9cf199421dae3c2f9972f94b2305976580d67", + "EnableSigned": true, + "EnableSsl": true + } + ] + }, + "Captcha": { + "Provider": "cloudflare", + "ApiKey": "0x4AAAAAABCDUdOujj4feOb_", + "ApiSecret": "0x4AAAAAABCDUWABiJQweqlB7tYq-IqIm8U" + }, + "Notifications": { + "Topic": "dev.solsynth.solian", + "Endpoint": "http://localhost:8088" + }, + "Email": { + "Server": "smtp4dev.orb.local", + "Port": 25, + "UseSsl": false, + "Username": "no-reply@mail.solsynth.dev", + "Password": "password", + "FromAddress": "no-reply@mail.solsynth.dev", + "FromName": "Alphabot", + "SubjectPrefix": "Solar Network" + }, + "RealtimeChat": { + "Endpoint": "https://solar-network-im44o8gq.livekit.cloud", + "ApiKey": "APIs6TiL8wj3A4j", + "ApiSecret": "SffxRneIwTnlHPtEf3zicmmv3LUEl7xXael4PvWZrEhE" + }, + "GeoIp": { + "DatabasePath": "./Keys/GeoLite2-City.mmdb" + }, + "Oidc": { + "Google": { + "ClientId": "961776991058-963m1qin2vtp8fv693b5fdrab5hmpl89.apps.googleusercontent.com", + "ClientSecret": "" + }, + "Apple": { + "ClientId": "dev.solsynth.solian", + "TeamId": "W7HPZ53V6B", + "KeyId": "B668YP4KBG", + "PrivateKeyPath": "./Keys/Solarpass.p8" + }, + "Microsoft": { + "ClientId": "YOUR_MICROSOFT_CLIENT_ID", + "ClientSecret": "YOUR_MICROSOFT_CLIENT_SECRET", + "DiscoveryEndpoint": "YOUR_MICROSOFT_DISCOVERY_ENDPOINT" + } + }, + "Payment": { + "Auth": { + "Afdian": "" + }, + "Subscriptions": { + "Afdian": { + "7d17aae23c9611f0b5705254001e7c00": "solian.stellar.primary", + "7dfae4743c9611f0b3a55254001e7c00": "solian.stellar.nova", + "141713ee3d6211f085b352540025c377": "solian.stellar.supernova" + } + } + }, + "KnownProxies": [ + "127.0.0.1", + "::1" + ] +} diff --git a/DysonNetwork.Pass/Auth/AuthServiceGrpc.cs b/DysonNetwork.Pass/Auth/AuthServiceGrpc.cs index 0e023ad..996dab4 100644 --- a/DysonNetwork.Pass/Auth/AuthServiceGrpc.cs +++ b/DysonNetwork.Pass/Auth/AuthServiceGrpc.cs @@ -1,27 +1,49 @@ +using DysonNetwork.Shared.Cache; using DysonNetwork.Shared.Proto; using Grpc.Core; using Microsoft.EntityFrameworkCore; +using NodaTime; namespace DysonNetwork.Pass.Auth; -public class AuthServiceGrpc(AuthService authService, AppDatabase db) : Shared.Proto.AuthService.AuthServiceBase +public class AuthServiceGrpc( + AuthService authService, + ICacheService cache, + AppDatabase db +) + : Shared.Proto.AuthService.AuthServiceBase { - public override async Task Authenticate(AuthenticateRequest request, ServerCallContext context) + public override async Task Authenticate( + AuthenticateRequest request, + ServerCallContext context + ) { if (!authService.ValidateToken(request.Token, out var sessionId)) - { - throw new RpcException(new Status(StatusCode.Unauthenticated, "Invalid token.")); - } + return new AuthenticateResponse { Valid = false, Message = "Invalid token." }; + + var session = await cache.GetAsync($"{DysonTokenAuthHandler.AuthCachePrefix}{sessionId}"); + if (session is not null) + return new AuthenticateResponse { Valid = true, Session = session.ToProtoValue() }; - var session = await db.AuthSessions + session = await db.AuthSessions .AsNoTracking() + .Include(e => e.Challenge) + .Include(e => e.Account) + .ThenInclude(e => e.Profile) .FirstOrDefaultAsync(s => s.Id == sessionId); - if (session == null) - { - throw new RpcException(new Status(StatusCode.NotFound, "Session not found.")); - } + return new AuthenticateResponse { Valid = false, Message = "Session was not found." }; + var now = SystemClock.Instance.GetCurrentInstant(); + if (session.ExpiredAt.HasValue && session.ExpiredAt < now) + return new AuthenticateResponse { Valid = false, Message = "Session has been expired." }; + + await cache.SetWithGroupsAsync( + $"auth:{sessionId}", + session, + [$"{Account.AccountService.AccountCachePrefix}{session.Account.Id}"], + TimeSpan.FromHours(1) + ); - return session.ToProtoValue(); + return new AuthenticateResponse { Valid = true, Session = session.ToProtoValue() }; } -} +} \ No newline at end of file diff --git a/DysonNetwork.Pass/DysonNetwork.Pass.csproj b/DysonNetwork.Pass/DysonNetwork.Pass.csproj index 3b281ce..e13db9f 100644 --- a/DysonNetwork.Pass/DysonNetwork.Pass.csproj +++ b/DysonNetwork.Pass/DysonNetwork.Pass.csproj @@ -8,33 +8,33 @@ - - - + + + - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + diff --git a/DysonNetwork.Pass/Permission/PermissionServiceGrpc.cs b/DysonNetwork.Pass/Permission/PermissionServiceGrpc.cs new file mode 100644 index 0000000..435c8f6 --- /dev/null +++ b/DysonNetwork.Pass/Permission/PermissionServiceGrpc.cs @@ -0,0 +1,96 @@ +using Grpc.Core; +using Microsoft.EntityFrameworkCore; +using DysonNetwork.Shared.Proto; +using Google.Protobuf.WellKnownTypes; +using System.Text.Json; +using NodaTime.Serialization.Protobuf; + +namespace DysonNetwork.Pass.Permission; + +public class PermissionServiceGrpc( + PermissionService permissionService, + AppDatabase db +) : DysonNetwork.Shared.Proto.PermissionService.PermissionServiceBase +{ + public override async Task HasPermission(HasPermissionRequest request, ServerCallContext context) + { + var hasPermission = await permissionService.HasPermissionAsync(request.Actor, request.Area, request.Key); + return new HasPermissionResponse { HasPermission = hasPermission }; + } + + public override async Task GetPermission(GetPermissionRequest request, ServerCallContext context) + { + var permissionValue = await permissionService.GetPermissionAsync(request.Actor, request.Area, request.Key); + return new GetPermissionResponse { Value = permissionValue != null ? Value.Parser.ParseJson(permissionValue.RootElement.GetRawText()) : null }; + } + + public override async Task AddPermissionNode(AddPermissionNodeRequest request, ServerCallContext context) + { + var node = await permissionService.AddPermissionNode( + request.Actor, + request.Area, + request.Key, + JsonDocument.Parse(request.Value.ToString()), // Convert Value to JsonDocument + request.ExpiredAt?.ToInstant(), + request.AffectedAt?.ToInstant() + ); + return new AddPermissionNodeResponse { Node = node.ToProtoValue() }; + } + + public override async Task AddPermissionNodeToGroup(AddPermissionNodeToGroupRequest request, ServerCallContext context) + { + var group = await db.PermissionGroups.FirstOrDefaultAsync(g => g.Id == Guid.Parse(request.Group.Id)); + if (group == null) + { + throw new RpcException(new Status(StatusCode.NotFound, "Permission group not found.")); + } + + var node = await permissionService.AddPermissionNodeToGroup( + group, + request.Actor, + request.Area, + request.Key, + JsonDocument.Parse(request.Value.ToString()), // Convert Value to JsonDocument + request.ExpiredAt?.ToInstant(), + request.AffectedAt?.ToInstant() + ); + return new AddPermissionNodeToGroupResponse { Node = node.ToProtoValue() }; + } + + public override async Task RemovePermissionNode(RemovePermissionNodeRequest request, ServerCallContext context) + { + await permissionService.RemovePermissionNode(request.Actor, request.Area, request.Key); + return new RemovePermissionNodeResponse { Success = true }; + } + + public override async Task RemovePermissionNodeFromGroup(RemovePermissionNodeFromGroupRequest request, ServerCallContext context) + { + var group = await db.PermissionGroups.FirstOrDefaultAsync(g => g.Id == Guid.Parse(request.Group.Id)); + if (group == null) + { + throw new RpcException(new Status(StatusCode.NotFound, "Permission group not found.")); + } + + await permissionService.RemovePermissionNodeFromGroup(group, request.Actor, request.Area, request.Key); + return new RemovePermissionNodeFromGroupResponse { Success = true }; + } +} + +public static class PermissionExtensions +{ + public static DysonNetwork.Shared.Proto.PermissionNode ToProtoValue(this PermissionNode node) + { + return new DysonNetwork.Shared.Proto.PermissionNode + { + Id = node.Id.ToString(), + Actor = node.Actor, + Area = node.Area, + Key = node.Key, + Value = Value.Parser.ParseJson(node.Value.RootElement.GetRawText()), + ExpiredAt = node.ExpiredAt?.ToTimestamp(), + AffectedAt = node.AffectedAt?.ToTimestamp(), + GroupId = node.GroupId?.ToString() ?? string.Empty + }; + } +} + diff --git a/DysonNetwork.Pass/Program.cs b/DysonNetwork.Pass/Program.cs index c6b9257..831b8f6 100644 --- a/DysonNetwork.Pass/Program.cs +++ b/DysonNetwork.Pass/Program.cs @@ -13,7 +13,7 @@ builder.ConfigureAppKestrel(); builder.Services.AddAppMetrics(); // Add application services -builder.Services.AddEtcdService(builder.Configuration); +builder.Services.AddRegistryService(builder.Configuration); builder.Services.AddAppServices(builder.Configuration); builder.Services.AddAppRateLimiting(); builder.Services.AddAppAuthentication(); diff --git a/DysonNetwork.Pusher/DysonNetwork.Pusher.csproj b/DysonNetwork.Pusher/DysonNetwork.Pusher.csproj index 718f65d..5bf2531 100644 --- a/DysonNetwork.Pusher/DysonNetwork.Pusher.csproj +++ b/DysonNetwork.Pusher/DysonNetwork.Pusher.csproj @@ -15,6 +15,7 @@ + diff --git a/DysonNetwork.Pusher/Program.cs b/DysonNetwork.Pusher/Program.cs index 6f8fc57..c3e1599 100644 --- a/DysonNetwork.Pusher/Program.cs +++ b/DysonNetwork.Pusher/Program.cs @@ -1,6 +1,7 @@ -using DysonNetwork.Pass.Startup; using DysonNetwork.Pusher; using DysonNetwork.Pusher.Startup; +using DysonNetwork.Shared.Auth; +using DysonNetwork.Shared.Registry; using Microsoft.EntityFrameworkCore; var builder = WebApplication.CreateBuilder(args); @@ -9,10 +10,12 @@ var builder = WebApplication.CreateBuilder(args); builder.ConfigureAppKestrel(); // Add application services +builder.Services.AddRegistryService(builder.Configuration); builder.Services.AddAppServices(builder.Configuration); builder.Services.AddAppRateLimiting(); builder.Services.AddAppAuthentication(); builder.Services.AddAppSwagger(); +builder.Services.AddDysonAuth(builder.Configuration); // Add flush handlers and websocket handlers builder.Services.AddAppFlushHandlers(); @@ -23,8 +26,6 @@ builder.Services.AddAppBusinessServices(); // Add scheduled jobs builder.Services.AddAppScheduledJobs(); -builder.Services.AddHostedService(); - var app = builder.Build(); // Run database migrations @@ -37,8 +38,6 @@ using (var scope = app.Services.CreateScope()) // Configure application middleware pipeline app.ConfigureAppMiddleware(builder.Configuration); -app.UseMiddleware(); - // Configure gRPC app.ConfigureGrpcServices(); diff --git a/DysonNetwork.Pusher/Startup/KestrelConfiguration.cs b/DysonNetwork.Pusher/Startup/KestrelConfiguration.cs index b042534..f35e4dd 100644 --- a/DysonNetwork.Pusher/Startup/KestrelConfiguration.cs +++ b/DysonNetwork.Pusher/Startup/KestrelConfiguration.cs @@ -1,4 +1,4 @@ -namespace DysonNetwork.Pass.Startup; +namespace DysonNetwork.Pusher.Startup; public static class KestrelConfiguration { diff --git a/DysonNetwork.Shared/Auth/AuthScheme.cs b/DysonNetwork.Shared/Auth/AuthScheme.cs new file mode 100644 index 0000000..69d4873 --- /dev/null +++ b/DysonNetwork.Shared/Auth/AuthScheme.cs @@ -0,0 +1,154 @@ +using System.Security.Claims; +using System.Text.Encodings.Web; +using DysonNetwork.Shared.Proto; +using Grpc.Core; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using SystemClock = NodaTime.SystemClock; + +namespace DysonNetwork.Shared.Auth; + +public class DysonTokenAuthOptions : AuthenticationSchemeOptions; + +public class DysonTokenAuthHandler( + IOptionsMonitor options, + ILoggerFactory logger, + UrlEncoder encoder, + ISystemClock clock, + AuthService.AuthServiceClient auth +) + : AuthenticationHandler(options, logger, encoder, clock) +{ + protected override async Task HandleAuthenticateAsync() + { + var tokenInfo = _ExtractToken(Request); + + if (tokenInfo == null || string.IsNullOrEmpty(tokenInfo.Token)) + return AuthenticateResult.Fail("No token was provided."); + + try + { + var now = SystemClock.Instance.GetCurrentInstant(); + + // Validate token and extract session ID + AuthSession session; + try + { + session = await ValidateToken(tokenInfo.Token); + } + catch (InvalidOperationException ex) + { + return AuthenticateResult.Fail(ex.Message); + } + catch (RpcException ex) + { + return AuthenticateResult.Fail($"Remote error: {ex.Status.StatusCode} - {ex.Status.Detail}"); + } + + // Store user and session in the HttpContext.Items for easy access in controllers + Context.Items["CurrentUser"] = session.Account; + Context.Items["CurrentSession"] = session; + Context.Items["CurrentTokenType"] = tokenInfo.Type.ToString(); + + // Create claims from the session + var claims = new List + { + new("user_id", session.Account.Id), + new("session_id", session.Id), + new("token_type", tokenInfo.Type.ToString()) + }; + + // return AuthenticateResult.Success(ticket); + return AuthenticateResult.NoResult(); + } + catch (Exception ex) + { + return AuthenticateResult.Fail($"Authentication failed: {ex.Message}"); + } + } + + private async Task ValidateToken(string token) + { + var resp = await auth.AuthenticateAsync(new AuthenticateRequest { Token = token }); + if (!resp.Valid) throw new InvalidOperationException(resp.Message); + if (resp.Session == null) throw new InvalidOperationException("Session not found."); + return resp.Session; + } + + private static byte[] Base64UrlDecode(string base64Url) + { + var padded = base64Url + .Replace('-', '+') + .Replace('_', '/'); + + switch (padded.Length % 4) + { + case 2: padded += "=="; break; + case 3: padded += "="; break; + } + + return Convert.FromBase64String(padded); + } + + private static TokenInfo? _ExtractToken(HttpRequest request) + { + // Check for token in query parameters + if (request.Query.TryGetValue(AuthConstants.TokenQueryParamName, out var queryToken)) + { + return new TokenInfo + { + Token = queryToken.ToString(), + Type = TokenType.AuthKey + }; + } + + + // Check for token in Authorization header + var authHeader = request.Headers["Authorization"].ToString(); + if (!string.IsNullOrEmpty(authHeader)) + { + if (authHeader.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase)) + { + var token = authHeader["Bearer ".Length..].Trim(); + var parts = token.Split('.'); + + return new TokenInfo + { + Token = token, + Type = parts.Length == 3 ? TokenType.OidcKey : TokenType.AuthKey + }; + } + else if (authHeader.StartsWith("AtField ", StringComparison.OrdinalIgnoreCase)) + { + return new TokenInfo + { + Token = authHeader["AtField ".Length..].Trim(), + Type = TokenType.AuthKey + }; + } + else if (authHeader.StartsWith("AkField ", StringComparison.OrdinalIgnoreCase)) + { + return new TokenInfo + { + Token = authHeader["AkField ".Length..].Trim(), + Type = TokenType.ApiKey + }; + } + } + + // Check for token in cookies + if (request.Cookies.TryGetValue(AuthConstants.CookieTokenName, out var cookieToken)) + { + return new TokenInfo + { + Token = cookieToken, + Type = cookieToken.Count(c => c == '.') == 2 ? TokenType.OidcKey : TokenType.AuthKey + }; + } + + + return null; + } +} \ No newline at end of file diff --git a/DysonNetwork.Shared/Auth/Startup.cs b/DysonNetwork.Shared/Auth/Startup.cs new file mode 100644 index 0000000..b5894fb --- /dev/null +++ b/DysonNetwork.Shared/Auth/Startup.cs @@ -0,0 +1,35 @@ +using dotnet_etcd.interfaces; +using DysonNetwork.Shared.Proto; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace DysonNetwork.Shared.Auth; + +public static class DysonAuthStartup +{ + public static IServiceCollection AddDysonAuth( + this IServiceCollection services, + IConfiguration configuration + ) + { + services.AddSingleton(sp => + { + var etcdClient = sp.GetRequiredService(); + var config = sp.GetRequiredService(); + var clientCertPath = config["ClientCert:Path"]; + var clientKeyPath = config["ClientKey:Path"]; + var clientCertPassword = config["ClientCert:Password"]; + + return GrpcClientHelper.CreateAuthServiceClient(etcdClient, clientCertPath, clientKeyPath, clientCertPassword); + }); + + services.AddAuthentication(options => + { + options.DefaultAuthenticateScheme = AuthConstants.SchemeName; + options.DefaultChallengeScheme = AuthConstants.SchemeName; + }) + .AddScheme(AuthConstants.SchemeName, _ => { }); + + return services; + } +} \ No newline at end of file diff --git a/DysonNetwork.Shared/DysonNetwork.Shared.csproj b/DysonNetwork.Shared/DysonNetwork.Shared.csproj index 0c82947..38296a3 100644 --- a/DysonNetwork.Shared/DysonNetwork.Shared.csproj +++ b/DysonNetwork.Shared/DysonNetwork.Shared.csproj @@ -8,7 +8,7 @@ - + @@ -17,21 +17,22 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + + - - - + + + - - - + + + - + diff --git a/DysonNetwork.Shared/Middleware/AuthMiddleware.cs b/DysonNetwork.Shared/Middleware/AuthMiddleware.cs deleted file mode 100644 index 6356427..0000000 --- a/DysonNetwork.Shared/Middleware/AuthMiddleware.cs +++ /dev/null @@ -1,107 +0,0 @@ -using Grpc.Core; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Logging; -using DysonNetwork.Shared.Proto; -using System.Threading.Tasks; -using DysonNetwork.Shared.Auth; - -namespace DysonNetwork.Shared.Middleware; - -public class AuthMiddleware -{ - private readonly RequestDelegate _next; - private readonly ILogger _logger; - - public AuthMiddleware(RequestDelegate next, ILogger logger) - { - _next = next; - _logger = logger; - } - - public async Task InvokeAsync(HttpContext context, AuthService.AuthServiceClient authServiceClient) - { - var tokenInfo = _ExtractToken(context.Request); - - if (tokenInfo == null || string.IsNullOrEmpty(tokenInfo.Token)) - { - await _next(context); - return; - } - - try - { - var authSession = await authServiceClient.AuthenticateAsync(new AuthenticateRequest { Token = tokenInfo.Token }); - context.Items["AuthSession"] = authSession; - context.Items["CurrentTokenType"] = tokenInfo.Type.ToString(); - // Assuming AuthSession contains Account information or can be retrieved - // context.Items["CurrentUser"] = authSession.Account; // You might need to fetch Account separately if not embedded - } - catch (RpcException ex) - { - _logger.LogWarning(ex, "Authentication failed for token: {Token}", tokenInfo.Token); - // Optionally, you can return an unauthorized response here - // context.Response.StatusCode = StatusCodes.Status401Unauthorized; - // return; - } - - await _next(context); - } - - private TokenInfo? _ExtractToken(HttpRequest request) - { - // Check for token in query parameters - if (request.Query.TryGetValue(AuthConstants.TokenQueryParamName, out var queryToken)) - { - return new TokenInfo - { - Token = queryToken.ToString(), - Type = TokenType.AuthKey - }; - } - - // Check for token in Authorization header - var authHeader = request.Headers["Authorization"].ToString(); - if (!string.IsNullOrEmpty(authHeader)) - { - if (authHeader.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase)) - { - var token = authHeader["Bearer ".Length..].Trim(); - var parts = token.Split('.'); - - return new TokenInfo - { - Token = token, - Type = parts.Length == 3 ? TokenType.OidcKey : TokenType.AuthKey - }; - } - else if (authHeader.StartsWith("AtField ", StringComparison.OrdinalIgnoreCase)) - { - return new TokenInfo - { - Token = authHeader["AtField ".Length..].Trim(), - Type = TokenType.AuthKey - }; - } - else if (authHeader.StartsWith("AkField ", StringComparison.OrdinalIgnoreCase)) - { - return new TokenInfo - { - Token = authHeader["AkField ".Length..].Trim(), - Type = TokenType.ApiKey - }; - } - } - - // Check for token in cookies - if (request.Cookies.TryGetValue(AuthConstants.CookieTokenName, out var cookieToken)) - { - return new TokenInfo - { - Token = cookieToken, - Type = cookieToken.Count(c => c == '.') == 2 ? TokenType.OidcKey : TokenType.AuthKey - }; - } - - return null; - } -} diff --git a/DysonNetwork.Shared/Proto/auth.proto b/DysonNetwork.Shared/Proto/auth.proto index e96f35e..2b70f8c 100644 --- a/DysonNetwork.Shared/Proto/auth.proto +++ b/DysonNetwork.Shared/Proto/auth.proto @@ -6,17 +6,21 @@ option csharp_namespace = "DysonNetwork.Shared.Proto"; import "google/protobuf/timestamp.proto"; import "google/protobuf/wrappers.proto"; +import "google/protobuf/struct.proto"; + +import 'account.proto'; // Represents a user session message AuthSession { string id = 1; google.protobuf.StringValue label = 2; - google.protobuf.Timestamp last_granted_at = 3; - google.protobuf.Timestamp expired_at = 4; + optional google.protobuf.Timestamp last_granted_at = 3; + optional google.protobuf.Timestamp expired_at = 4; string account_id = 5; - string challenge_id = 6; - AuthChallenge challenge = 7; - google.protobuf.StringValue app_id = 8; + Account account = 6; + string challenge_id = 7; + AuthChallenge challenge = 8; + google.protobuf.StringValue app_id = 9; } // Represents an authentication challenge @@ -60,9 +64,111 @@ enum ChallengePlatform { } service AuthService { - rpc Authenticate(AuthenticateRequest) returns (AuthSession) {} + rpc Authenticate(AuthenticateRequest) returns (AuthenticateResponse) {} } message AuthenticateRequest { string token = 1; } + +message AuthenticateResponse { + bool valid = 1; + optional string message = 2; + optional AuthSession session = 3; +} + +// Permission related messages and services +message PermissionNode { + string id = 1; + string actor = 2; + string area = 3; + string key = 4; + google.protobuf.Value value = 5; // Using Value to represent generic type + google.protobuf.Timestamp expired_at = 6; + google.protobuf.Timestamp affected_at = 7; + string group_id = 8; // Optional group ID +} + +message PermissionGroup { + string id = 1; + string name = 2; + google.protobuf.Timestamp created_at = 3; +} + +message HasPermissionRequest { + string actor = 1; + string area = 2; + string key = 3; +} + +message HasPermissionResponse { + bool has_permission = 1; +} + +message GetPermissionRequest { + string actor = 1; + string area = 2; + string key = 3; +} + +message GetPermissionResponse { + google.protobuf.Value value = 1; // Using Value to represent generic type +} + +message AddPermissionNodeRequest { + string actor = 1; + string area = 2; + string key = 3; + google.protobuf.Value value = 4; + google.protobuf.Timestamp expired_at = 5; + google.protobuf.Timestamp affected_at = 6; +} + +message AddPermissionNodeResponse { + PermissionNode node = 1; +} + +message AddPermissionNodeToGroupRequest { + PermissionGroup group = 1; + string actor = 2; + string area = 3; + string key = 4; + google.protobuf.Value value = 5; + google.protobuf.Timestamp expired_at = 6; + google.protobuf.Timestamp affected_at = 7; +} + +message AddPermissionNodeToGroupResponse { + PermissionNode node = 1; +} + +message RemovePermissionNodeRequest { + string actor = 1; + string area = 2; + string key = 3; +} + +message RemovePermissionNodeResponse { + bool success = 1; +} + +message RemovePermissionNodeFromGroupRequest { + PermissionGroup group = 1; + string actor = 2; + string area = 3; + string key = 4; +} + +message RemovePermissionNodeFromGroupResponse { + bool success = 1; +} + +service PermissionService { + rpc HasPermission(HasPermissionRequest) returns (HasPermissionResponse) {} + rpc GetPermission(GetPermissionRequest) returns (GetPermissionResponse) {} + rpc AddPermissionNode(AddPermissionNodeRequest) returns (AddPermissionNodeResponse) {} + rpc AddPermissionNodeToGroup(AddPermissionNodeToGroupRequest) returns (AddPermissionNodeToGroupResponse) {} + rpc RemovePermissionNode(RemovePermissionNodeRequest) returns (RemovePermissionNodeResponse) {} + rpc RemovePermissionNodeFromGroup(RemovePermissionNodeFromGroupRequest) returns (RemovePermissionNodeFromGroupResponse) {} +} + diff --git a/DysonNetwork.Shared/Registry/RegistryHostedService.cs b/DysonNetwork.Shared/Registry/RegistryHostedService.cs new file mode 100644 index 0000000..da7d72d --- /dev/null +++ b/DysonNetwork.Shared/Registry/RegistryHostedService.cs @@ -0,0 +1,45 @@ +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace DysonNetwork.Shared.Registry; + +public class RegistryHostedService( + ServiceRegistry serviceRegistry, + IConfiguration configuration, + ILogger logger +) + : IHostedService +{ + public async Task StartAsync(CancellationToken cancellationToken) + { + var serviceName = configuration["Service:Name"]; + var serviceUrl = configuration["Service:Url"]; + + if (string.IsNullOrEmpty(serviceUrl) || string.IsNullOrEmpty(serviceName)) + { + logger.LogWarning("Service URL or Service Name was not configured. Skipping Etcd registration."); + return; + } + + logger.LogInformation("Registering service {ServiceName} at {ServiceUrl} with Etcd.", serviceName, serviceUrl); + try + { + await serviceRegistry.RegisterService(serviceName, serviceUrl); + logger.LogInformation("Service {ServiceName} registered successfully.", serviceName); + } + catch (Exception ex) + { + logger.LogError(ex, "Failed to register service {ServiceName} with Etcd.", serviceName); + } + } + + public async Task StopAsync(CancellationToken cancellationToken) + { + // The lease will expire automatically if the service stops ungracefully. + var serviceName = configuration["Service:Name"]; + if (serviceName is not null) + await serviceRegistry.UnregisterService(serviceName); + logger.LogInformation("Service registration hosted service is stopping."); + } +} \ No newline at end of file diff --git a/DysonNetwork.Shared/Registry/Startup.cs b/DysonNetwork.Shared/Registry/Startup.cs index cceef2d..a5e050c 100644 --- a/DysonNetwork.Shared/Registry/Startup.cs +++ b/DysonNetwork.Shared/Registry/Startup.cs @@ -4,9 +4,9 @@ using Microsoft.Extensions.DependencyInjection; namespace DysonNetwork.Shared.Registry; -public static class EtcdStartup +public static class RegistryStartup { - public static IServiceCollection AddEtcdService( + public static IServiceCollection AddRegistryService( this IServiceCollection services, IConfiguration configuration ) @@ -17,6 +17,7 @@ public static class EtcdStartup options.UseInsecureChannel = configuration.GetValue("Etcd:Insecure"); }); services.AddSingleton(); + services.AddHostedService(); return services; } diff --git a/DysonNetwork.Sphere/DysonNetwork.Sphere.csproj b/DysonNetwork.Sphere/DysonNetwork.Sphere.csproj index 1c05644..b65e109 100644 --- a/DysonNetwork.Sphere/DysonNetwork.Sphere.csproj +++ b/DysonNetwork.Sphere/DysonNetwork.Sphere.csproj @@ -29,7 +29,7 @@ - + all @@ -40,10 +40,10 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - + + + + @@ -66,16 +66,16 @@ - - - - + + + + - + diff --git a/DysonNetwork.sln b/DysonNetwork.sln index 4b2f2be..1ef9fd7 100644 --- a/DysonNetwork.sln +++ b/DysonNetwork.sln @@ -13,6 +13,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DysonNetwork.Shared", "Dyso EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DysonNetwork.Pusher", "DysonNetwork.Pusher\DysonNetwork.Pusher.csproj", "{D5DAFB0D-487E-48EF-BA2F-C581C846F63B}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DysonNetwork.Drive", "DysonNetwork.Drive\DysonNetwork.Drive.csproj", "{8DE0B783-8852-494D-B90A-201ABBB71202}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -35,5 +37,9 @@ Global {D5DAFB0D-487E-48EF-BA2F-C581C846F63B}.Debug|Any CPU.Build.0 = Debug|Any CPU {D5DAFB0D-487E-48EF-BA2F-C581C846F63B}.Release|Any CPU.ActiveCfg = Release|Any CPU {D5DAFB0D-487E-48EF-BA2F-C581C846F63B}.Release|Any CPU.Build.0 = Release|Any CPU + {8DE0B783-8852-494D-B90A-201ABBB71202}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8DE0B783-8852-494D-B90A-201ABBB71202}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8DE0B783-8852-494D-B90A-201ABBB71202}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8DE0B783-8852-494D-B90A-201ABBB71202}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/DysonNetwork.sln.DotSettings.user b/DysonNetwork.sln.DotSettings.user index 95dd6c2..caeddec 100644 --- a/DysonNetwork.sln.DotSettings.user +++ b/DysonNetwork.sln.DotSettings.user @@ -3,7 +3,9 @@ ForceIncluded ForceIncluded ForceIncluded + ForceIncluded ForceIncluded + ForceIncluded ForceIncluded ForceIncluded ForceIncluded From 03e26ef93c250a92ce38c4913523f66a4e4cccc9 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Sun, 13 Jul 2025 21:51:16 +0800 Subject: [PATCH 09/42] :recycle: I have no idea what I have done --- DysonNetwork.Drive/DysonNetwork.Drive.csproj | 52 +- ...0250713121317_InitialMigration.Designer.cs | 189 ++ .../20250713121317_InitialMigration.cs | 89 + .../Migrations/AppDatabaseModelSnapshot.cs | 186 ++ DysonNetwork.Drive/appsettings.json | 11 +- DysonNetwork.Pass/Account/AccountService.cs | 25 +- .../Account/NotificationService.cs | 292 --- DysonNetwork.Pass/DysonNetwork.Pass.csproj | 4 + DysonNetwork.Pass/Email/EmailService.cs | 38 +- ...0250713121237_InitialMigration.Designer.cs | 1977 +++++++++++++++++ .../20250713121237_InitialMigration.cs | 998 +++++++++ .../Migrations/AppDatabaseModelSnapshot.cs | 1974 ++++++++++++++++ DysonNetwork.Pass/Program.cs | 2 - .../ServiceRegistrationHostedService.cs | 56 - DysonNetwork.Pass/appsettings.json | 65 +- .../DysonNetwork.Pusher.csproj | 6 +- ...0250713122638_InitialMigration.Designer.cs | 151 ++ .../20250713122638_InitialMigration.cs | 75 + .../Migrations/AppDatabaseModelSnapshot.cs | 148 ++ .../Notification/Notification.cs | 1 - .../Notification}/NotificationController.cs | 6 +- .../Startup/ApplicationConfiguration.cs | 2 - .../Startup/ServiceCollectionExtensions.cs | 16 +- .../ServiceRegistrationHostedService.cs | 56 - DysonNetwork.Pusher/appsettings.json | 111 +- DysonNetwork.Shared/Auth/Startup.cs | 9 +- .../Registry/RegistryHostedService.cs | 19 +- .../Registry/ServiceRegistry.cs | 18 +- DysonNetwork.Sphere/appsettings.json | 2 +- 29 files changed, 5933 insertions(+), 645 deletions(-) create mode 100644 DysonNetwork.Drive/Migrations/20250713121317_InitialMigration.Designer.cs create mode 100644 DysonNetwork.Drive/Migrations/20250713121317_InitialMigration.cs create mode 100644 DysonNetwork.Drive/Migrations/AppDatabaseModelSnapshot.cs delete mode 100644 DysonNetwork.Pass/Account/NotificationService.cs create mode 100644 DysonNetwork.Pass/Migrations/20250713121237_InitialMigration.Designer.cs create mode 100644 DysonNetwork.Pass/Migrations/20250713121237_InitialMigration.cs create mode 100644 DysonNetwork.Pass/Migrations/AppDatabaseModelSnapshot.cs delete mode 100644 DysonNetwork.Pass/Startup/ServiceRegistrationHostedService.cs create mode 100644 DysonNetwork.Pusher/Migrations/20250713122638_InitialMigration.Designer.cs create mode 100644 DysonNetwork.Pusher/Migrations/20250713122638_InitialMigration.cs create mode 100644 DysonNetwork.Pusher/Migrations/AppDatabaseModelSnapshot.cs rename {DysonNetwork.Pass/Account => DysonNetwork.Pusher/Notification}/NotificationController.cs (97%) delete mode 100644 DysonNetwork.Pusher/Startup/ServiceRegistrationHostedService.cs diff --git a/DysonNetwork.Drive/DysonNetwork.Drive.csproj b/DysonNetwork.Drive/DysonNetwork.Drive.csproj index 199eb2d..9edd5ba 100644 --- a/DysonNetwork.Drive/DysonNetwork.Drive.csproj +++ b/DysonNetwork.Drive/DysonNetwork.Drive.csproj @@ -11,7 +11,11 @@ - + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -20,30 +24,30 @@ - - + + - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + diff --git a/DysonNetwork.Drive/Migrations/20250713121317_InitialMigration.Designer.cs b/DysonNetwork.Drive/Migrations/20250713121317_InitialMigration.Designer.cs new file mode 100644 index 0000000..cdcbf47 --- /dev/null +++ b/DysonNetwork.Drive/Migrations/20250713121317_InitialMigration.Designer.cs @@ -0,0 +1,189 @@ +// +using System; +using System.Collections.Generic; +using DysonNetwork.Drive; +using DysonNetwork.Drive.Storage; +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.Drive.Migrations +{ + [DbContext(typeof(AppDatabase))] + [Migration("20250713121317_InitialMigration")] + partial class InitialMigration + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.7") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "postgis"); + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("DysonNetwork.Drive.Storage.CloudFile", b => + { + b.Property("Id") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .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("Description") + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("description"); + + b.Property>("FileMeta") + .HasColumnType("jsonb") + .HasColumnName("file_meta"); + + b.Property("HasCompression") + .HasColumnType("boolean") + .HasColumnName("has_compression"); + + b.Property("Hash") + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("hash"); + + b.Property("IsMarkedRecycle") + .HasColumnType("boolean") + .HasColumnName("is_marked_recycle"); + + b.Property("MimeType") + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("mime_type"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("name"); + + b.Property>("SensitiveMarks") + .HasColumnType("jsonb") + .HasColumnName("sensitive_marks"); + + b.Property("Size") + .HasColumnType("bigint") + .HasColumnName("size"); + + b.Property("StorageId") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("storage_id"); + + b.Property("StorageUrl") + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("storage_url"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.Property("UploadedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("uploaded_at"); + + b.Property("UploadedTo") + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("uploaded_to"); + + b.Property>("UserMeta") + .HasColumnType("jsonb") + .HasColumnName("user_meta"); + + b.HasKey("Id") + .HasName("pk_files"); + + b.ToTable("files", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Drive.Storage.CloudFileReference", 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("ExpiredAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("expired_at"); + + b.Property("FileId") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("file_id"); + + b.Property("ResourceId") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("resource_id"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.Property("Usage") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("usage"); + + b.HasKey("Id") + .HasName("pk_file_references"); + + b.HasIndex("FileId") + .HasDatabaseName("ix_file_references_file_id"); + + b.ToTable("file_references", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Drive.Storage.CloudFileReference", b => + { + b.HasOne("DysonNetwork.Drive.Storage.CloudFile", "File") + .WithMany() + .HasForeignKey("FileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_file_references_files_file_id"); + + b.Navigation("File"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/DysonNetwork.Drive/Migrations/20250713121317_InitialMigration.cs b/DysonNetwork.Drive/Migrations/20250713121317_InitialMigration.cs new file mode 100644 index 0000000..48977cd --- /dev/null +++ b/DysonNetwork.Drive/Migrations/20250713121317_InitialMigration.cs @@ -0,0 +1,89 @@ +using System; +using System.Collections.Generic; +using DysonNetwork.Drive.Storage; +using Microsoft.EntityFrameworkCore.Migrations; +using NodaTime; + +#nullable disable + +namespace DysonNetwork.Drive.Migrations +{ + /// + public partial class InitialMigration : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterDatabase() + .Annotation("Npgsql:PostgresExtension:postgis", ",,"); + + migrationBuilder.CreateTable( + name: "files", + columns: table => new + { + id = table.Column(type: "character varying(32)", maxLength: 32, nullable: false), + name = table.Column(type: "character varying(1024)", maxLength: 1024, nullable: false), + description = table.Column(type: "character varying(4096)", maxLength: 4096, nullable: true), + file_meta = table.Column>(type: "jsonb", nullable: true), + user_meta = table.Column>(type: "jsonb", nullable: true), + sensitive_marks = table.Column>(type: "jsonb", nullable: true), + mime_type = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), + hash = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), + size = table.Column(type: "bigint", nullable: false), + uploaded_at = table.Column(type: "timestamp with time zone", nullable: true), + uploaded_to = table.Column(type: "character varying(128)", maxLength: 128, nullable: true), + has_compression = table.Column(type: "boolean", nullable: false), + is_marked_recycle = table.Column(type: "boolean", nullable: false), + storage_id = table.Column(type: "character varying(32)", maxLength: 32, nullable: true), + storage_url = table.Column(type: "character varying(4096)", maxLength: 4096, nullable: true), + account_id = table.Column(type: "uuid", nullable: false), + created_at = table.Column(type: "timestamp with time zone", nullable: false), + updated_at = table.Column(type: "timestamp with time zone", nullable: false), + deleted_at = table.Column(type: "timestamp with time zone", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_files", x => x.id); + }); + + migrationBuilder.CreateTable( + name: "file_references", + columns: table => new + { + id = table.Column(type: "uuid", nullable: false), + file_id = table.Column(type: "character varying(32)", maxLength: 32, nullable: false), + usage = table.Column(type: "character varying(1024)", maxLength: 1024, nullable: false), + resource_id = table.Column(type: "character varying(1024)", maxLength: 1024, nullable: false), + expired_at = table.Column(type: "timestamp with time zone", nullable: true), + created_at = table.Column(type: "timestamp with time zone", nullable: false), + updated_at = table.Column(type: "timestamp with time zone", nullable: false), + deleted_at = table.Column(type: "timestamp with time zone", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_file_references", x => x.id); + table.ForeignKey( + name: "fk_file_references_files_file_id", + column: x => x.file_id, + principalTable: "files", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "ix_file_references_file_id", + table: "file_references", + column: "file_id"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "file_references"); + + migrationBuilder.DropTable( + name: "files"); + } + } +} diff --git a/DysonNetwork.Drive/Migrations/AppDatabaseModelSnapshot.cs b/DysonNetwork.Drive/Migrations/AppDatabaseModelSnapshot.cs new file mode 100644 index 0000000..e8d6e77 --- /dev/null +++ b/DysonNetwork.Drive/Migrations/AppDatabaseModelSnapshot.cs @@ -0,0 +1,186 @@ +// +using System; +using System.Collections.Generic; +using DysonNetwork.Drive; +using DysonNetwork.Drive.Storage; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using NodaTime; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace DysonNetwork.Drive.Migrations +{ + [DbContext(typeof(AppDatabase))] + partial class AppDatabaseModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.7") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "postgis"); + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("DysonNetwork.Drive.Storage.CloudFile", b => + { + b.Property("Id") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .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("Description") + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("description"); + + b.Property>("FileMeta") + .HasColumnType("jsonb") + .HasColumnName("file_meta"); + + b.Property("HasCompression") + .HasColumnType("boolean") + .HasColumnName("has_compression"); + + b.Property("Hash") + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("hash"); + + b.Property("IsMarkedRecycle") + .HasColumnType("boolean") + .HasColumnName("is_marked_recycle"); + + b.Property("MimeType") + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("mime_type"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("name"); + + b.Property>("SensitiveMarks") + .HasColumnType("jsonb") + .HasColumnName("sensitive_marks"); + + b.Property("Size") + .HasColumnType("bigint") + .HasColumnName("size"); + + b.Property("StorageId") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("storage_id"); + + b.Property("StorageUrl") + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("storage_url"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.Property("UploadedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("uploaded_at"); + + b.Property("UploadedTo") + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("uploaded_to"); + + b.Property>("UserMeta") + .HasColumnType("jsonb") + .HasColumnName("user_meta"); + + b.HasKey("Id") + .HasName("pk_files"); + + b.ToTable("files", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Drive.Storage.CloudFileReference", 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("ExpiredAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("expired_at"); + + b.Property("FileId") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("file_id"); + + b.Property("ResourceId") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("resource_id"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.Property("Usage") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("usage"); + + b.HasKey("Id") + .HasName("pk_file_references"); + + b.HasIndex("FileId") + .HasDatabaseName("ix_file_references_file_id"); + + b.ToTable("file_references", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Drive.Storage.CloudFileReference", b => + { + b.HasOne("DysonNetwork.Drive.Storage.CloudFile", "File") + .WithMany() + .HasForeignKey("FileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_file_references_files_file_id"); + + b.Navigation("File"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/DysonNetwork.Drive/appsettings.json b/DysonNetwork.Drive/appsettings.json index 82c1088..721bba8 100644 --- a/DysonNetwork.Drive/appsettings.json +++ b/DysonNetwork.Drive/appsettings.json @@ -10,7 +10,8 @@ "AllowedHosts": "*", "ConnectionStrings": { "App": "Host=localhost;Port=5432;Database=dyson_network;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60", - "FastRetrieve": "localhost:6379" + "FastRetrieve": "localhost:6379", + "Etcd": "etcd.orb.local:2379" }, "Authentication": { "Schemes": { @@ -125,5 +126,11 @@ "KnownProxies": [ "127.0.0.1", "::1" - ] + ], + "Service": { + "Name": "DysonNetwork.Drive", + "Url": "http://localhost:5216", + "ClientCert": "../Certificates/client.crt", + "ClientKey": "../Certificates/client.key" + } } diff --git a/DysonNetwork.Pass/Account/AccountService.cs b/DysonNetwork.Pass/Account/AccountService.cs index f376468..5aea9c6 100644 --- a/DysonNetwork.Pass/Account/AccountService.cs +++ b/DysonNetwork.Pass/Account/AccountService.cs @@ -5,11 +5,13 @@ using DysonNetwork.Pass.Email; using DysonNetwork.Pass.Localization; using DysonNetwork.Pass.Permission; using DysonNetwork.Shared.Cache; +using DysonNetwork.Shared.Proto; using EFCore.BulkExtensions; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Localization; using NodaTime; using OtpNet; +using AuthSession = DysonNetwork.Pass.Auth.AuthSession; namespace DysonNetwork.Pass.Account; @@ -17,8 +19,8 @@ public class AccountService( AppDatabase db, MagicSpellService spells, AccountUsernameService uname, - NotificationService nty, EmailService mailer, + PusherService.PusherServiceClient pusher, IStringLocalizer localizer, ICacheService cache, ILogger logger @@ -353,13 +355,18 @@ public class AccountService( if (await _GetFactorCode(factor) is not null) throw new InvalidOperationException("A factor code has been sent and in active duration."); - await nty.SendNotification( - account, - "auth.verification", - localizer["AuthCodeTitle"], - null, - localizer["AuthCodeBody", code], - save: true + await pusher.SendPushNotificationToUserAsync( + new SendPushNotificationToUserRequest + { + UserId = account.Id.ToString(), + Notification = new PushNotification + { + Topic = "auth.verification", + Title = localizer["AuthCodeTitle"], + Body = localizer["AuthCodeBody", code], + IsSavable = false + } + } ); await _SetFactorCode(factor, code, TimeSpan.FromMinutes(5)); break; @@ -489,7 +496,7 @@ public class AccountService( .ToListAsync(); if (session.Challenge.DeviceId is not null) - await nty.UnsubscribePushNotifications(session.Challenge.DeviceId); + await pusher.UnsubscribePushNotifications(session.Challenge.DeviceId); // The current session should be included in the sessions' list await db.AuthSessions diff --git a/DysonNetwork.Pass/Account/NotificationService.cs b/DysonNetwork.Pass/Account/NotificationService.cs deleted file mode 100644 index 202ded6..0000000 --- a/DysonNetwork.Pass/Account/NotificationService.cs +++ /dev/null @@ -1,292 +0,0 @@ -using System.Text; -using System.Text.Json; -using DysonNetwork.Pass; -using EFCore.BulkExtensions; -using Microsoft.EntityFrameworkCore; -using NodaTime; - -namespace DysonNetwork.Pass.Account; - -public class NotificationService( - AppDatabase db, - IHttpClientFactory httpFactory, - IConfiguration config -) -{ - private readonly string _notifyTopic = config["Notifications:Topic"]!; - private readonly Uri _notifyEndpoint = new(config["Notifications:Endpoint"]!); - - public async Task UnsubscribePushNotifications(string deviceId) - { - await db.NotificationPushSubscriptions - .Where(s => s.DeviceId == deviceId) - .ExecuteDeleteAsync(); - } - - public async Task SubscribePushNotification( - Account account, - NotificationPushProvider provider, - string deviceId, - string deviceToken - ) - { - var now = SystemClock.Instance.GetCurrentInstant(); - - // First check if a matching subscription exists - var existingSubscription = await db.NotificationPushSubscriptions - .Where(s => s.AccountId == account.Id) - .Where(s => s.DeviceId == deviceId || s.DeviceToken == deviceToken) - .FirstOrDefaultAsync(); - - if (existingSubscription is not null) - { - // Update the existing subscription directly in the database - await db.NotificationPushSubscriptions - .Where(s => s.Id == existingSubscription.Id) - .ExecuteUpdateAsync(setters => setters - .SetProperty(s => s.DeviceId, deviceId) - .SetProperty(s => s.DeviceToken, deviceToken) - .SetProperty(s => s.UpdatedAt, now)); - - // Return the updated subscription - existingSubscription.DeviceId = deviceId; - existingSubscription.DeviceToken = deviceToken; - existingSubscription.UpdatedAt = now; - return existingSubscription; - } - - var subscription = new NotificationPushSubscription - { - DeviceId = deviceId, - DeviceToken = deviceToken, - Provider = provider, - AccountId = account.Id, - }; - - db.NotificationPushSubscriptions.Add(subscription); - await db.SaveChangesAsync(); - - return subscription; - } - - public async Task SendNotification( - Account account, - string topic, - string? title = null, - string? subtitle = null, - string? content = null, - Dictionary? meta = null, - string? actionUri = null, - bool isSilent = false, - bool save = true - ) - { - if (title is null && subtitle is null && content is null) - throw new ArgumentException("Unable to send notification that completely empty."); - - meta ??= new Dictionary(); - if (actionUri is not null) meta["action_uri"] = actionUri; - - var notification = new Notification - { - Topic = topic, - Title = title, - Subtitle = subtitle, - Content = content, - Meta = meta, - AccountId = account.Id, - }; - - if (save) - { - db.Add(notification); - await db.SaveChangesAsync(); - } - - if (!isSilent) _ = DeliveryNotification(notification); - - return notification; - } - - public async Task DeliveryNotification(Notification notification) - { - // Pushing the notification - var subscribers = await db.NotificationPushSubscriptions - .Where(s => s.AccountId == notification.AccountId) - .ToListAsync(); - - await _PushNotification(notification, subscribers); - } - - public async Task MarkNotificationsViewed(ICollection notifications) - { - var now = SystemClock.Instance.GetCurrentInstant(); - var id = notifications.Where(n => n.ViewedAt == null).Select(n => n.Id).ToList(); - if (id.Count == 0) return; - - await db.Notifications - .Where(n => id.Contains(n.Id)) - .ExecuteUpdateAsync(s => s.SetProperty(n => n.ViewedAt, now) - ); - } - - public async Task BroadcastNotification(Notification notification, bool save = false) - { - var accounts = await db.Accounts.ToListAsync(); - - if (save) - { - var notifications = accounts.Select(x => - { - var newNotification = new Notification - { - Topic = notification.Topic, - Title = notification.Title, - Subtitle = notification.Subtitle, - Content = notification.Content, - Meta = notification.Meta, - Priority = notification.Priority, - Account = x, - AccountId = x.Id - }; - return newNotification; - }).ToList(); - await db.BulkInsertAsync(notifications); - } - - foreach (var account in accounts) - { - notification.Account = account; - notification.AccountId = account.Id; - } - - var subscribers = await db.NotificationPushSubscriptions - .ToListAsync(); - await _PushNotification(notification, subscribers); - } - - public async Task SendNotificationBatch(Notification notification, List accounts, bool save = false) - { - if (save) - { - var notifications = accounts.Select(x => - { - var newNotification = new Notification - { - Topic = notification.Topic, - Title = notification.Title, - Subtitle = notification.Subtitle, - Content = notification.Content, - Meta = notification.Meta, - Priority = notification.Priority, - Account = x, - AccountId = x.Id - }; - return newNotification; - }).ToList(); - await db.BulkInsertAsync(notifications); - } - - foreach (var account in accounts) - { - notification.Account = account; - notification.AccountId = account.Id; - } - - var accountsId = accounts.Select(x => x.Id).ToList(); - var subscribers = await db.NotificationPushSubscriptions - .Where(s => accountsId.Contains(s.AccountId)) - .ToListAsync(); - await _PushNotification(notification, subscribers); - } - - private List> _BuildNotificationPayload(Notification notification, - IEnumerable subscriptions) - { - var subDict = subscriptions - .GroupBy(x => x.Provider) - .ToDictionary(x => x.Key, x => x.ToList()); - - var notifications = subDict.Select(value => - { - var platformCode = value.Key switch - { - NotificationPushProvider.Apple => 1, - NotificationPushProvider.Google => 2, - _ => throw new InvalidOperationException($"Unknown push provider: {value.Key}") - }; - - var tokens = value.Value.Select(x => x.DeviceToken).ToList(); - return _BuildNotificationPayload(notification, platformCode, tokens); - }).ToList(); - - return notifications.ToList(); - } - - private Dictionary _BuildNotificationPayload(Notification notification, int platformCode, - IEnumerable deviceTokens) - { - var alertDict = new Dictionary(); - var dict = new Dictionary - { - ["notif_id"] = notification.Id.ToString(), - ["apns_id"] = notification.Id.ToString(), - ["topic"] = _notifyTopic, - ["tokens"] = deviceTokens, - ["data"] = new Dictionary - { - ["type"] = notification.Topic, - ["meta"] = notification.Meta ?? new Dictionary(), - }, - ["mutable_content"] = true, - ["priority"] = notification.Priority >= 5 ? "high" : "normal", - }; - - if (!string.IsNullOrWhiteSpace(notification.Title)) - { - dict["title"] = notification.Title; - alertDict["title"] = notification.Title; - } - - if (!string.IsNullOrWhiteSpace(notification.Content)) - { - dict["message"] = notification.Content; - alertDict["body"] = notification.Content; - } - - if (!string.IsNullOrWhiteSpace(notification.Subtitle)) - { - dict["message"] = $"{notification.Subtitle}\n{dict["message"]}"; - alertDict["subtitle"] = notification.Subtitle; - } - - if (notification.Priority >= 5) - dict["name"] = "default"; - - dict["platform"] = platformCode; - dict["alert"] = alertDict; - - return dict; - } - - private async Task _PushNotification(Notification notification, - IEnumerable subscriptions) - { - var subList = subscriptions.ToList(); - if (subList.Count == 0) return; - - var requestDict = new Dictionary - { - ["notifications"] = _BuildNotificationPayload(notification, subList) - }; - - var client = httpFactory.CreateClient(); - client.BaseAddress = _notifyEndpoint; - var request = await client.PostAsync("/push", new StringContent( - JsonSerializer.Serialize(requestDict), - Encoding.UTF8, - "application/json" - )); - request.EnsureSuccessStatusCode(); - } -} \ No newline at end of file diff --git a/DysonNetwork.Pass/DysonNetwork.Pass.csproj b/DysonNetwork.Pass/DysonNetwork.Pass.csproj index e13db9f..b781b58 100644 --- a/DysonNetwork.Pass/DysonNetwork.Pass.csproj +++ b/DysonNetwork.Pass/DysonNetwork.Pass.csproj @@ -9,6 +9,10 @@ + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + diff --git a/DysonNetwork.Pass/Email/EmailService.cs b/DysonNetwork.Pass/Email/EmailService.cs index b8bb428..7cf34ee 100644 --- a/DysonNetwork.Pass/Email/EmailService.cs +++ b/DysonNetwork.Pass/Email/EmailService.cs @@ -1,33 +1,23 @@ using dotnet_etcd; +using dotnet_etcd.interfaces; using DysonNetwork.Shared.Proto; using Microsoft.AspNetCore.Components; namespace DysonNetwork.Pass.Email; -public class EmailService +public class EmailService( + IEtcdClient etcd, + RazorViewRenderer viewRenderer, + IConfiguration configuration, + ILogger logger +) { - private readonly PusherService.PusherServiceClient _client; - private readonly RazorViewRenderer _viewRenderer; - private readonly ILogger _logger; + private readonly PusherService.PusherServiceClient _client = GrpcClientHelper.CreatePusherServiceClient( + etcd, + configuration["Service:CertPath"]!, + configuration["Service:KeyPath"]! + ).GetAwaiter().GetResult(); - public EmailService( - EtcdClient etcd, - RazorViewRenderer viewRenderer, - IConfiguration configuration, - ILogger logger, - PusherService.PusherServiceClient client - ) - { - _client = GrpcClientHelper.CreatePusherServiceClient( - etcd, - configuration["Service:CertPath"]!, - configuration["Service:KeyPath"]! - ).GetAwaiter().GetResult(); - _viewRenderer = viewRenderer; - _logger = logger; - _client = client; - } - public async Task SendEmailAsync( string? recipientName, string recipientEmail, @@ -57,12 +47,12 @@ public class EmailService { try { - var htmlBody = await _viewRenderer.RenderComponentToStringAsync(model); + var htmlBody = await viewRenderer.RenderComponentToStringAsync(model); await SendEmailAsync(recipientName, recipientEmail, subject, htmlBody); } catch (Exception err) { - _logger.LogError(err, "Failed to render email template..."); + logger.LogError(err, "Failed to render email template..."); throw; } } diff --git a/DysonNetwork.Pass/Migrations/20250713121237_InitialMigration.Designer.cs b/DysonNetwork.Pass/Migrations/20250713121237_InitialMigration.Designer.cs new file mode 100644 index 0000000..21c1ca2 --- /dev/null +++ b/DysonNetwork.Pass/Migrations/20250713121237_InitialMigration.Designer.cs @@ -0,0 +1,1977 @@ +// +using System; +using System.Collections.Generic; +using System.Text.Json; +using DysonNetwork.Pass; +using DysonNetwork.Pass.Account; +using DysonNetwork.Pass.Developer; +using DysonNetwork.Pass.Wallet; +using DysonNetwork.Shared.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using NetTopologySuite.Geometries; +using NodaTime; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace DysonNetwork.Pass.Migrations +{ + [DbContext(typeof(AppDatabase))] + [Migration("20250713121237_InitialMigration")] + partial class InitialMigration + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.7") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "postgis"); + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("DysonNetwork.Pass.Account.AbuseReport", 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.Pass.Account.Account", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("ActivatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("activated_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("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("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.Pass.Account.AccountAuthFactor", 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.Pass.Account.AccountBadge", 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.Pass.Account.AccountConnection", 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.Pass.Account.AccountContact", 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("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.Pass.Account.AccountProfile", 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("BackgroundId") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("background_id"); + + 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("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("PictureId") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("picture_id"); + + b.Property("Pronouns") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("pronouns"); + + 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("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.Pass.Account.ActionLog", 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("geometry") + .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.Pass.Account.CheckInResult", 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("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.Pass.Account.MagicSpell", 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.Pass.Account.Notification", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("AccountId") + .HasColumnType("uuid") + .HasColumnName("account_id"); + + b.Property("Content") + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .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>("Meta") + .HasColumnType("jsonb") + .HasColumnName("meta"); + + b.Property("Priority") + .HasColumnType("integer") + .HasColumnName("priority"); + + b.Property("Subtitle") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)") + .HasColumnName("subtitle"); + + b.Property("Title") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("title"); + + b.Property("Topic") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("topic"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.Property("ViewedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("viewed_at"); + + b.HasKey("Id") + .HasName("pk_notifications"); + + b.HasIndex("AccountId") + .HasDatabaseName("ix_notifications_account_id"); + + b.ToTable("notifications", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Pass.Account.NotificationPushSubscription", 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(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("device_id"); + + b.Property("DeviceToken") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("device_token"); + + b.Property("LastUsedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_used_at"); + + b.Property("Provider") + .HasColumnType("integer") + .HasColumnName("provider"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.HasKey("Id") + .HasName("pk_notification_push_subscriptions"); + + b.HasIndex("AccountId") + .HasDatabaseName("ix_notification_push_subscriptions_account_id"); + + b.HasIndex("DeviceToken", "DeviceId", "AccountId") + .IsUnique() + .HasDatabaseName("ix_notification_push_subscriptions_device_token_device_id_acco"); + + b.ToTable("notification_push_subscriptions", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Pass.Account.Relationship", 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.Pass.Account.Status", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("AccountId") + .HasColumnType("uuid") + .HasColumnName("account_id"); + + 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("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("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.Pass.Auth.AuthChallenge", 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") + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("device_id"); + + 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("geometry") + .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("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_challenges"); + + b.HasIndex("AccountId") + .HasDatabaseName("ix_auth_challenges_account_id"); + + b.ToTable("auth_challenges", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Pass.Auth.AuthSession", 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("ChallengeId") + .HasColumnType("uuid") + .HasColumnName("challenge_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("Label") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("label"); + + b.Property("LastGrantedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_granted_at"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.HasKey("Id") + .HasName("pk_auth_sessions"); + + b.HasIndex("AccountId") + .HasDatabaseName("ix_auth_sessions_account_id"); + + b.HasIndex("AppId") + .HasDatabaseName("ix_auth_sessions_app_id"); + + b.HasIndex("ChallengeId") + .HasDatabaseName("ix_auth_sessions_challenge_id"); + + b.ToTable("auth_sessions", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Pass.Developer.CustomApp", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("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") + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("description"); + + b.Property("Links") + .HasColumnType("jsonb") + .HasColumnName("links"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("name"); + + b.Property("OauthConfig") + .HasColumnType("jsonb") + .HasColumnName("oauth_config"); + + b.Property("Picture") + .HasColumnType("jsonb") + .HasColumnName("picture"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("slug"); + + b.Property("Status") + .HasColumnType("integer") + .HasColumnName("status"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.Property("Verification") + .HasColumnType("jsonb") + .HasColumnName("verification"); + + b.HasKey("Id") + .HasName("pk_custom_apps"); + + b.ToTable("custom_apps", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Pass.Developer.CustomAppSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("AppId") + .HasColumnType("uuid") + .HasColumnName("app_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("Description") + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("description"); + + b.Property("ExpiredAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("expired_at"); + + b.Property("IsOidc") + .HasColumnType("boolean") + .HasColumnName("is_oidc"); + + b.Property("Secret") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("secret"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.HasKey("Id") + .HasName("pk_custom_app_secrets"); + + b.HasIndex("AppId") + .HasDatabaseName("ix_custom_app_secrets_app_id"); + + b.ToTable("custom_app_secrets", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Pass.Permission.PermissionGroup", 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.Pass.Permission.PermissionGroupMember", 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.Pass.Permission.PermissionNode", 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("Area") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("area"); + + 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("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", "Area", "Actor") + .HasDatabaseName("ix_permission_nodes_key_area_actor"); + + b.ToTable("permission_nodes", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Pass.Wallet.Coupon", 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.Pass.Wallet.Order", 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("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.Pass.Wallet.Subscription", 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.ToTable("wallet_subscriptions", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Pass.Wallet.Transaction", 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.Pass.Wallet.Wallet", 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.Pass.Wallet.WalletPocket", 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.Pass.Account.AbuseReport", b => + { + b.HasOne("DysonNetwork.Pass.Account.Account", "Account") + .WithMany() + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_abuse_reports_accounts_account_id"); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("DysonNetwork.Pass.Account.AccountAuthFactor", b => + { + b.HasOne("DysonNetwork.Pass.Account.Account", "Account") + .WithMany("AuthFactors") + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_account_auth_factors_accounts_account_id"); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("DysonNetwork.Pass.Account.AccountBadge", b => + { + b.HasOne("DysonNetwork.Pass.Account.Account", "Account") + .WithMany("Badges") + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_badges_accounts_account_id"); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("DysonNetwork.Pass.Account.AccountConnection", b => + { + b.HasOne("DysonNetwork.Pass.Account.Account", "Account") + .WithMany("Connections") + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_account_connections_accounts_account_id"); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("DysonNetwork.Pass.Account.AccountContact", b => + { + b.HasOne("DysonNetwork.Pass.Account.Account", "Account") + .WithMany("Contacts") + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_account_contacts_accounts_account_id"); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("DysonNetwork.Pass.Account.AccountProfile", b => + { + b.HasOne("DysonNetwork.Pass.Account.Account", "Account") + .WithOne("Profile") + .HasForeignKey("DysonNetwork.Pass.Account.AccountProfile", "AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_account_profiles_accounts_account_id"); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("DysonNetwork.Pass.Account.ActionLog", b => + { + b.HasOne("DysonNetwork.Pass.Account.Account", "Account") + .WithMany() + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_action_logs_accounts_account_id"); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("DysonNetwork.Pass.Account.CheckInResult", b => + { + b.HasOne("DysonNetwork.Pass.Account.Account", "Account") + .WithMany() + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_account_check_in_results_accounts_account_id"); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("DysonNetwork.Pass.Account.MagicSpell", b => + { + b.HasOne("DysonNetwork.Pass.Account.Account", "Account") + .WithMany() + .HasForeignKey("AccountId") + .HasConstraintName("fk_magic_spells_accounts_account_id"); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("DysonNetwork.Pass.Account.Notification", b => + { + b.HasOne("DysonNetwork.Pass.Account.Account", "Account") + .WithMany() + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_notifications_accounts_account_id"); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("DysonNetwork.Pass.Account.NotificationPushSubscription", b => + { + b.HasOne("DysonNetwork.Pass.Account.Account", "Account") + .WithMany() + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_notification_push_subscriptions_accounts_account_id"); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("DysonNetwork.Pass.Account.Relationship", b => + { + b.HasOne("DysonNetwork.Pass.Account.Account", "Account") + .WithMany("OutgoingRelationships") + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_account_relationships_accounts_account_id"); + + b.HasOne("DysonNetwork.Pass.Account.Account", "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.Pass.Account.Status", b => + { + b.HasOne("DysonNetwork.Pass.Account.Account", "Account") + .WithMany() + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_account_statuses_accounts_account_id"); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("DysonNetwork.Pass.Auth.AuthChallenge", b => + { + b.HasOne("DysonNetwork.Pass.Account.Account", "Account") + .WithMany("Challenges") + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_auth_challenges_accounts_account_id"); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("DysonNetwork.Pass.Auth.AuthSession", b => + { + b.HasOne("DysonNetwork.Pass.Account.Account", "Account") + .WithMany("Sessions") + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_auth_sessions_accounts_account_id"); + + b.HasOne("DysonNetwork.Pass.Developer.CustomApp", "App") + .WithMany() + .HasForeignKey("AppId") + .HasConstraintName("fk_auth_sessions_custom_apps_app_id"); + + b.HasOne("DysonNetwork.Pass.Auth.AuthChallenge", "Challenge") + .WithMany() + .HasForeignKey("ChallengeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_auth_sessions_auth_challenges_challenge_id"); + + b.Navigation("Account"); + + b.Navigation("App"); + + b.Navigation("Challenge"); + }); + + modelBuilder.Entity("DysonNetwork.Pass.Developer.CustomAppSecret", b => + { + b.HasOne("DysonNetwork.Pass.Developer.CustomApp", "App") + .WithMany("Secrets") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_custom_app_secrets_custom_apps_app_id"); + + b.Navigation("App"); + }); + + modelBuilder.Entity("DysonNetwork.Pass.Permission.PermissionGroupMember", b => + { + b.HasOne("DysonNetwork.Pass.Permission.PermissionGroup", "Group") + .WithMany("Members") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_permission_group_members_permission_groups_group_id"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("DysonNetwork.Pass.Permission.PermissionNode", b => + { + b.HasOne("DysonNetwork.Pass.Permission.PermissionGroup", "Group") + .WithMany("Nodes") + .HasForeignKey("GroupId") + .HasConstraintName("fk_permission_nodes_permission_groups_group_id"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("DysonNetwork.Pass.Wallet.Order", b => + { + b.HasOne("DysonNetwork.Pass.Wallet.Wallet", "PayeeWallet") + .WithMany() + .HasForeignKey("PayeeWalletId") + .HasConstraintName("fk_payment_orders_wallets_payee_wallet_id"); + + b.HasOne("DysonNetwork.Pass.Wallet.Transaction", "Transaction") + .WithMany() + .HasForeignKey("TransactionId") + .HasConstraintName("fk_payment_orders_payment_transactions_transaction_id"); + + b.Navigation("PayeeWallet"); + + b.Navigation("Transaction"); + }); + + modelBuilder.Entity("DysonNetwork.Pass.Wallet.Subscription", b => + { + b.HasOne("DysonNetwork.Pass.Account.Account", "Account") + .WithMany() + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_wallet_subscriptions_accounts_account_id"); + + b.HasOne("DysonNetwork.Pass.Wallet.Coupon", "Coupon") + .WithMany() + .HasForeignKey("CouponId") + .HasConstraintName("fk_wallet_subscriptions_wallet_coupons_coupon_id"); + + b.Navigation("Account"); + + b.Navigation("Coupon"); + }); + + modelBuilder.Entity("DysonNetwork.Pass.Wallet.Transaction", b => + { + b.HasOne("DysonNetwork.Pass.Wallet.Wallet", "PayeeWallet") + .WithMany() + .HasForeignKey("PayeeWalletId") + .HasConstraintName("fk_payment_transactions_wallets_payee_wallet_id"); + + b.HasOne("DysonNetwork.Pass.Wallet.Wallet", "PayerWallet") + .WithMany() + .HasForeignKey("PayerWalletId") + .HasConstraintName("fk_payment_transactions_wallets_payer_wallet_id"); + + b.Navigation("PayeeWallet"); + + b.Navigation("PayerWallet"); + }); + + modelBuilder.Entity("DysonNetwork.Pass.Wallet.Wallet", b => + { + b.HasOne("DysonNetwork.Pass.Account.Account", "Account") + .WithMany() + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_wallets_accounts_account_id"); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("DysonNetwork.Pass.Wallet.WalletPocket", b => + { + b.HasOne("DysonNetwork.Pass.Wallet.Wallet", "Wallet") + .WithMany("Pockets") + .HasForeignKey("WalletId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_wallet_pockets_wallets_wallet_id"); + + b.Navigation("Wallet"); + }); + + modelBuilder.Entity("DysonNetwork.Pass.Account.Account", 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.Pass.Developer.CustomApp", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("DysonNetwork.Pass.Permission.PermissionGroup", b => + { + b.Navigation("Members"); + + b.Navigation("Nodes"); + }); + + modelBuilder.Entity("DysonNetwork.Pass.Wallet.Wallet", b => + { + b.Navigation("Pockets"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/DysonNetwork.Pass/Migrations/20250713121237_InitialMigration.cs b/DysonNetwork.Pass/Migrations/20250713121237_InitialMigration.cs new file mode 100644 index 0000000..493dcc9 --- /dev/null +++ b/DysonNetwork.Pass/Migrations/20250713121237_InitialMigration.cs @@ -0,0 +1,998 @@ +using System; +using System.Collections.Generic; +using System.Text.Json; +using DysonNetwork.Pass.Account; +using DysonNetwork.Pass.Developer; +using DysonNetwork.Pass.Wallet; +using DysonNetwork.Shared.Data; +using Microsoft.EntityFrameworkCore.Migrations; +using NetTopologySuite.Geometries; +using NodaTime; + +#nullable disable + +namespace DysonNetwork.Pass.Migrations +{ + /// + public partial class InitialMigration : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterDatabase() + .Annotation("Npgsql:PostgresExtension:postgis", ",,"); + + migrationBuilder.CreateTable( + name: "accounts", + columns: table => new + { + id = table.Column(type: "uuid", nullable: false), + name = table.Column(type: "character varying(256)", maxLength: 256, nullable: false), + nick = table.Column(type: "character varying(256)", maxLength: 256, nullable: false), + language = table.Column(type: "character varying(32)", maxLength: 32, nullable: false), + activated_at = table.Column(type: "timestamp with time zone", nullable: true), + is_superuser = table.Column(type: "boolean", nullable: false), + created_at = table.Column(type: "timestamp with time zone", nullable: false), + updated_at = table.Column(type: "timestamp with time zone", nullable: false), + deleted_at = table.Column(type: "timestamp with time zone", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_accounts", x => x.id); + }); + + migrationBuilder.CreateTable( + name: "custom_apps", + columns: table => new + { + id = table.Column(type: "uuid", nullable: false), + slug = table.Column(type: "character varying(1024)", maxLength: 1024, nullable: false), + name = table.Column(type: "character varying(1024)", maxLength: 1024, nullable: false), + description = table.Column(type: "character varying(4096)", maxLength: 4096, nullable: true), + status = table.Column(type: "integer", nullable: false), + picture = table.Column(type: "jsonb", nullable: true), + background = table.Column(type: "jsonb", nullable: true), + verification = table.Column(type: "jsonb", nullable: true), + oauth_config = table.Column(type: "jsonb", nullable: true), + links = table.Column(type: "jsonb", nullable: true), + created_at = table.Column(type: "timestamp with time zone", nullable: false), + updated_at = table.Column(type: "timestamp with time zone", nullable: false), + deleted_at = table.Column(type: "timestamp with time zone", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_custom_apps", x => x.id); + }); + + migrationBuilder.CreateTable( + name: "permission_groups", + columns: table => new + { + id = table.Column(type: "uuid", nullable: false), + key = table.Column(type: "character varying(1024)", maxLength: 1024, nullable: false), + created_at = table.Column(type: "timestamp with time zone", nullable: false), + updated_at = table.Column(type: "timestamp with time zone", nullable: false), + deleted_at = table.Column(type: "timestamp with time zone", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_permission_groups", x => x.id); + }); + + migrationBuilder.CreateTable( + name: "wallet_coupons", + columns: table => new + { + id = table.Column(type: "uuid", nullable: false), + identifier = table.Column(type: "character varying(4096)", maxLength: 4096, nullable: true), + code = table.Column(type: "character varying(1024)", maxLength: 1024, nullable: true), + affected_at = table.Column(type: "timestamp with time zone", nullable: true), + expired_at = table.Column(type: "timestamp with time zone", nullable: true), + discount_amount = table.Column(type: "numeric", nullable: true), + discount_rate = table.Column(type: "double precision", nullable: true), + max_usage = table.Column(type: "integer", nullable: true), + created_at = table.Column(type: "timestamp with time zone", nullable: false), + updated_at = table.Column(type: "timestamp with time zone", nullable: false), + deleted_at = table.Column(type: "timestamp with time zone", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_wallet_coupons", x => x.id); + }); + + migrationBuilder.CreateTable( + name: "abuse_reports", + columns: table => new + { + id = table.Column(type: "uuid", nullable: false), + resource_identifier = table.Column(type: "character varying(4096)", maxLength: 4096, nullable: false), + type = table.Column(type: "integer", nullable: false), + reason = table.Column(type: "character varying(8192)", maxLength: 8192, nullable: false), + resolved_at = table.Column(type: "timestamp with time zone", nullable: true), + resolution = table.Column(type: "character varying(8192)", maxLength: 8192, nullable: true), + account_id = table.Column(type: "uuid", nullable: false), + created_at = table.Column(type: "timestamp with time zone", nullable: false), + updated_at = table.Column(type: "timestamp with time zone", nullable: false), + deleted_at = table.Column(type: "timestamp with time zone", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_abuse_reports", x => x.id); + table.ForeignKey( + name: "fk_abuse_reports_accounts_account_id", + column: x => x.account_id, + principalTable: "accounts", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "account_auth_factors", + columns: table => new + { + id = table.Column(type: "uuid", nullable: false), + type = table.Column(type: "integer", nullable: false), + secret = table.Column(type: "character varying(8196)", maxLength: 8196, nullable: true), + config = table.Column>(type: "jsonb", nullable: true), + trustworthy = table.Column(type: "integer", nullable: false), + enabled_at = table.Column(type: "timestamp with time zone", nullable: true), + expired_at = table.Column(type: "timestamp with time zone", nullable: true), + account_id = table.Column(type: "uuid", nullable: false), + created_at = table.Column(type: "timestamp with time zone", nullable: false), + updated_at = table.Column(type: "timestamp with time zone", nullable: false), + deleted_at = table.Column(type: "timestamp with time zone", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_account_auth_factors", x => x.id); + table.ForeignKey( + name: "fk_account_auth_factors_accounts_account_id", + column: x => x.account_id, + principalTable: "accounts", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "account_check_in_results", + columns: table => new + { + id = table.Column(type: "uuid", nullable: false), + level = table.Column(type: "integer", nullable: false), + reward_points = table.Column(type: "numeric", nullable: true), + reward_experience = table.Column(type: "integer", nullable: true), + tips = table.Column>(type: "jsonb", nullable: false), + account_id = table.Column(type: "uuid", nullable: false), + created_at = table.Column(type: "timestamp with time zone", nullable: false), + updated_at = table.Column(type: "timestamp with time zone", nullable: false), + deleted_at = table.Column(type: "timestamp with time zone", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_account_check_in_results", x => x.id); + table.ForeignKey( + name: "fk_account_check_in_results_accounts_account_id", + column: x => x.account_id, + principalTable: "accounts", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "account_connections", + columns: table => new + { + id = table.Column(type: "uuid", nullable: false), + provider = table.Column(type: "character varying(4096)", maxLength: 4096, nullable: false), + provided_identifier = table.Column(type: "character varying(8192)", maxLength: 8192, nullable: false), + meta = table.Column>(type: "jsonb", nullable: true), + access_token = table.Column(type: "character varying(4096)", maxLength: 4096, nullable: true), + refresh_token = table.Column(type: "character varying(4096)", maxLength: 4096, nullable: true), + last_used_at = table.Column(type: "timestamp with time zone", nullable: true), + account_id = table.Column(type: "uuid", nullable: false), + created_at = table.Column(type: "timestamp with time zone", nullable: false), + updated_at = table.Column(type: "timestamp with time zone", nullable: false), + deleted_at = table.Column(type: "timestamp with time zone", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_account_connections", x => x.id); + table.ForeignKey( + name: "fk_account_connections_accounts_account_id", + column: x => x.account_id, + principalTable: "accounts", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "account_contacts", + columns: table => new + { + id = table.Column(type: "uuid", nullable: false), + type = table.Column(type: "integer", nullable: false), + verified_at = table.Column(type: "timestamp with time zone", nullable: true), + is_primary = table.Column(type: "boolean", nullable: false), + content = table.Column(type: "character varying(1024)", maxLength: 1024, nullable: false), + account_id = table.Column(type: "uuid", nullable: false), + created_at = table.Column(type: "timestamp with time zone", nullable: false), + updated_at = table.Column(type: "timestamp with time zone", nullable: false), + deleted_at = table.Column(type: "timestamp with time zone", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_account_contacts", x => x.id); + table.ForeignKey( + name: "fk_account_contacts_accounts_account_id", + column: x => x.account_id, + principalTable: "accounts", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "account_profiles", + columns: table => new + { + id = table.Column(type: "uuid", nullable: false), + first_name = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), + middle_name = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), + last_name = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), + bio = table.Column(type: "character varying(4096)", maxLength: 4096, nullable: true), + gender = table.Column(type: "character varying(1024)", maxLength: 1024, nullable: true), + pronouns = table.Column(type: "character varying(1024)", maxLength: 1024, nullable: true), + time_zone = table.Column(type: "character varying(1024)", maxLength: 1024, nullable: true), + location = table.Column(type: "character varying(1024)", maxLength: 1024, nullable: true), + birthday = table.Column(type: "timestamp with time zone", nullable: true), + last_seen_at = table.Column(type: "timestamp with time zone", nullable: true), + verification = table.Column(type: "jsonb", nullable: true), + active_badge = table.Column(type: "jsonb", nullable: true), + experience = table.Column(type: "integer", nullable: false), + picture_id = table.Column(type: "character varying(32)", maxLength: 32, nullable: true), + background_id = table.Column(type: "character varying(32)", maxLength: 32, nullable: true), + picture = table.Column(type: "jsonb", nullable: true), + background = table.Column(type: "jsonb", nullable: true), + account_id = table.Column(type: "uuid", nullable: false), + created_at = table.Column(type: "timestamp with time zone", nullable: false), + updated_at = table.Column(type: "timestamp with time zone", nullable: false), + deleted_at = table.Column(type: "timestamp with time zone", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_account_profiles", x => x.id); + table.ForeignKey( + name: "fk_account_profiles_accounts_account_id", + column: x => x.account_id, + principalTable: "accounts", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "account_relationships", + columns: table => new + { + account_id = table.Column(type: "uuid", nullable: false), + related_id = table.Column(type: "uuid", nullable: false), + expired_at = table.Column(type: "timestamp with time zone", nullable: true), + status = table.Column(type: "smallint", nullable: false), + created_at = table.Column(type: "timestamp with time zone", nullable: false), + updated_at = table.Column(type: "timestamp with time zone", nullable: false), + deleted_at = table.Column(type: "timestamp with time zone", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_account_relationships", x => new { x.account_id, x.related_id }); + table.ForeignKey( + name: "fk_account_relationships_accounts_account_id", + column: x => x.account_id, + principalTable: "accounts", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "fk_account_relationships_accounts_related_id", + column: x => x.related_id, + principalTable: "accounts", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "account_statuses", + columns: table => new + { + id = table.Column(type: "uuid", nullable: false), + attitude = table.Column(type: "integer", nullable: false), + is_invisible = table.Column(type: "boolean", nullable: false), + is_not_disturb = table.Column(type: "boolean", nullable: false), + label = table.Column(type: "character varying(1024)", maxLength: 1024, nullable: true), + cleared_at = table.Column(type: "timestamp with time zone", nullable: true), + account_id = table.Column(type: "uuid", nullable: false), + created_at = table.Column(type: "timestamp with time zone", nullable: false), + updated_at = table.Column(type: "timestamp with time zone", nullable: false), + deleted_at = table.Column(type: "timestamp with time zone", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_account_statuses", x => x.id); + table.ForeignKey( + name: "fk_account_statuses_accounts_account_id", + column: x => x.account_id, + principalTable: "accounts", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "action_logs", + columns: table => new + { + id = table.Column(type: "uuid", nullable: false), + action = table.Column(type: "character varying(4096)", maxLength: 4096, nullable: false), + meta = table.Column>(type: "jsonb", nullable: false), + user_agent = table.Column(type: "character varying(512)", maxLength: 512, nullable: true), + ip_address = table.Column(type: "character varying(128)", maxLength: 128, nullable: true), + location = table.Column(type: "geometry", nullable: true), + account_id = table.Column(type: "uuid", nullable: false), + session_id = table.Column(type: "uuid", nullable: true), + created_at = table.Column(type: "timestamp with time zone", nullable: false), + updated_at = table.Column(type: "timestamp with time zone", nullable: false), + deleted_at = table.Column(type: "timestamp with time zone", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_action_logs", x => x.id); + table.ForeignKey( + name: "fk_action_logs_accounts_account_id", + column: x => x.account_id, + principalTable: "accounts", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "auth_challenges", + columns: table => new + { + id = table.Column(type: "uuid", nullable: false), + expired_at = table.Column(type: "timestamp with time zone", nullable: true), + step_remain = table.Column(type: "integer", nullable: false), + step_total = table.Column(type: "integer", nullable: false), + failed_attempts = table.Column(type: "integer", nullable: false), + platform = table.Column(type: "integer", nullable: false), + type = table.Column(type: "integer", nullable: false), + blacklist_factors = table.Column>(type: "jsonb", nullable: false), + audiences = table.Column>(type: "jsonb", nullable: false), + scopes = table.Column>(type: "jsonb", nullable: false), + ip_address = table.Column(type: "character varying(128)", maxLength: 128, nullable: true), + user_agent = table.Column(type: "character varying(512)", maxLength: 512, nullable: true), + device_id = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), + nonce = table.Column(type: "character varying(1024)", maxLength: 1024, nullable: true), + location = table.Column(type: "geometry", nullable: true), + account_id = table.Column(type: "uuid", nullable: false), + created_at = table.Column(type: "timestamp with time zone", nullable: false), + updated_at = table.Column(type: "timestamp with time zone", nullable: false), + deleted_at = table.Column(type: "timestamp with time zone", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_auth_challenges", x => x.id); + table.ForeignKey( + name: "fk_auth_challenges_accounts_account_id", + column: x => x.account_id, + principalTable: "accounts", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "badges", + columns: table => new + { + id = table.Column(type: "uuid", nullable: false), + type = table.Column(type: "character varying(1024)", maxLength: 1024, nullable: false), + label = table.Column(type: "character varying(1024)", maxLength: 1024, nullable: true), + caption = table.Column(type: "character varying(4096)", maxLength: 4096, nullable: true), + meta = table.Column>(type: "jsonb", nullable: false), + activated_at = table.Column(type: "timestamp with time zone", nullable: true), + expired_at = table.Column(type: "timestamp with time zone", nullable: true), + account_id = table.Column(type: "uuid", nullable: false), + created_at = table.Column(type: "timestamp with time zone", nullable: false), + updated_at = table.Column(type: "timestamp with time zone", nullable: false), + deleted_at = table.Column(type: "timestamp with time zone", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_badges", x => x.id); + table.ForeignKey( + name: "fk_badges_accounts_account_id", + column: x => x.account_id, + principalTable: "accounts", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "magic_spells", + columns: table => new + { + id = table.Column(type: "uuid", nullable: false), + spell = table.Column(type: "character varying(1024)", maxLength: 1024, nullable: false), + type = table.Column(type: "integer", nullable: false), + expires_at = table.Column(type: "timestamp with time zone", nullable: true), + affected_at = table.Column(type: "timestamp with time zone", nullable: true), + meta = table.Column>(type: "jsonb", nullable: false), + account_id = table.Column(type: "uuid", nullable: true), + created_at = table.Column(type: "timestamp with time zone", nullable: false), + updated_at = table.Column(type: "timestamp with time zone", nullable: false), + deleted_at = table.Column(type: "timestamp with time zone", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_magic_spells", x => x.id); + table.ForeignKey( + name: "fk_magic_spells_accounts_account_id", + column: x => x.account_id, + principalTable: "accounts", + principalColumn: "id"); + }); + + migrationBuilder.CreateTable( + name: "notification_push_subscriptions", + columns: table => new + { + id = table.Column(type: "uuid", nullable: false), + device_id = table.Column(type: "character varying(4096)", maxLength: 4096, nullable: false), + device_token = table.Column(type: "character varying(4096)", maxLength: 4096, nullable: false), + provider = table.Column(type: "integer", nullable: false), + last_used_at = table.Column(type: "timestamp with time zone", nullable: true), + account_id = table.Column(type: "uuid", nullable: false), + created_at = table.Column(type: "timestamp with time zone", nullable: false), + updated_at = table.Column(type: "timestamp with time zone", nullable: false), + deleted_at = table.Column(type: "timestamp with time zone", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_notification_push_subscriptions", x => x.id); + table.ForeignKey( + name: "fk_notification_push_subscriptions_accounts_account_id", + column: x => x.account_id, + principalTable: "accounts", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "notifications", + columns: table => new + { + id = table.Column(type: "uuid", nullable: false), + topic = table.Column(type: "character varying(1024)", maxLength: 1024, nullable: false), + title = table.Column(type: "character varying(1024)", maxLength: 1024, nullable: true), + subtitle = table.Column(type: "character varying(2048)", maxLength: 2048, nullable: true), + content = table.Column(type: "character varying(4096)", maxLength: 4096, nullable: true), + meta = table.Column>(type: "jsonb", nullable: true), + priority = table.Column(type: "integer", nullable: false), + viewed_at = table.Column(type: "timestamp with time zone", nullable: true), + account_id = table.Column(type: "uuid", nullable: false), + created_at = table.Column(type: "timestamp with time zone", nullable: false), + updated_at = table.Column(type: "timestamp with time zone", nullable: false), + deleted_at = table.Column(type: "timestamp with time zone", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_notifications", x => x.id); + table.ForeignKey( + name: "fk_notifications_accounts_account_id", + column: x => x.account_id, + principalTable: "accounts", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "wallets", + columns: table => new + { + id = table.Column(type: "uuid", nullable: false), + account_id = table.Column(type: "uuid", nullable: false), + created_at = table.Column(type: "timestamp with time zone", nullable: false), + updated_at = table.Column(type: "timestamp with time zone", nullable: false), + deleted_at = table.Column(type: "timestamp with time zone", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_wallets", x => x.id); + table.ForeignKey( + name: "fk_wallets_accounts_account_id", + column: x => x.account_id, + principalTable: "accounts", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "custom_app_secrets", + columns: table => new + { + id = table.Column(type: "uuid", nullable: false), + secret = table.Column(type: "character varying(1024)", maxLength: 1024, nullable: false), + description = table.Column(type: "character varying(4096)", maxLength: 4096, nullable: true), + expired_at = table.Column(type: "timestamp with time zone", nullable: true), + is_oidc = table.Column(type: "boolean", nullable: false), + app_id = table.Column(type: "uuid", nullable: false), + created_at = table.Column(type: "timestamp with time zone", nullable: false), + updated_at = table.Column(type: "timestamp with time zone", nullable: false), + deleted_at = table.Column(type: "timestamp with time zone", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_custom_app_secrets", x => x.id); + table.ForeignKey( + name: "fk_custom_app_secrets_custom_apps_app_id", + column: x => x.app_id, + principalTable: "custom_apps", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "permission_group_members", + columns: table => new + { + group_id = table.Column(type: "uuid", nullable: false), + actor = table.Column(type: "character varying(1024)", maxLength: 1024, nullable: false), + expired_at = table.Column(type: "timestamp with time zone", nullable: true), + affected_at = table.Column(type: "timestamp with time zone", nullable: true), + created_at = table.Column(type: "timestamp with time zone", nullable: false), + updated_at = table.Column(type: "timestamp with time zone", nullable: false), + deleted_at = table.Column(type: "timestamp with time zone", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_permission_group_members", x => new { x.group_id, x.actor }); + table.ForeignKey( + name: "fk_permission_group_members_permission_groups_group_id", + column: x => x.group_id, + principalTable: "permission_groups", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "permission_nodes", + columns: table => new + { + id = table.Column(type: "uuid", nullable: false), + actor = table.Column(type: "character varying(1024)", maxLength: 1024, nullable: false), + area = table.Column(type: "character varying(1024)", maxLength: 1024, nullable: false), + key = table.Column(type: "character varying(1024)", maxLength: 1024, nullable: false), + value = table.Column(type: "jsonb", nullable: false), + expired_at = table.Column(type: "timestamp with time zone", nullable: true), + affected_at = table.Column(type: "timestamp with time zone", nullable: true), + group_id = table.Column(type: "uuid", nullable: true), + created_at = table.Column(type: "timestamp with time zone", nullable: false), + updated_at = table.Column(type: "timestamp with time zone", nullable: false), + deleted_at = table.Column(type: "timestamp with time zone", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_permission_nodes", x => x.id); + table.ForeignKey( + name: "fk_permission_nodes_permission_groups_group_id", + column: x => x.group_id, + principalTable: "permission_groups", + principalColumn: "id"); + }); + + migrationBuilder.CreateTable( + name: "wallet_subscriptions", + columns: table => new + { + id = table.Column(type: "uuid", nullable: false), + begun_at = table.Column(type: "timestamp with time zone", nullable: false), + ended_at = table.Column(type: "timestamp with time zone", nullable: true), + identifier = table.Column(type: "character varying(4096)", maxLength: 4096, nullable: false), + is_active = table.Column(type: "boolean", nullable: false), + is_free_trial = table.Column(type: "boolean", nullable: false), + status = table.Column(type: "integer", nullable: false), + payment_method = table.Column(type: "character varying(4096)", maxLength: 4096, nullable: false), + payment_details = table.Column(type: "jsonb", nullable: false), + base_price = table.Column(type: "numeric", nullable: false), + coupon_id = table.Column(type: "uuid", nullable: true), + renewal_at = table.Column(type: "timestamp with time zone", nullable: true), + account_id = table.Column(type: "uuid", nullable: false), + created_at = table.Column(type: "timestamp with time zone", nullable: false), + updated_at = table.Column(type: "timestamp with time zone", nullable: false), + deleted_at = table.Column(type: "timestamp with time zone", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_wallet_subscriptions", x => x.id); + table.ForeignKey( + name: "fk_wallet_subscriptions_accounts_account_id", + column: x => x.account_id, + principalTable: "accounts", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "fk_wallet_subscriptions_wallet_coupons_coupon_id", + column: x => x.coupon_id, + principalTable: "wallet_coupons", + principalColumn: "id"); + }); + + migrationBuilder.CreateTable( + name: "auth_sessions", + columns: table => new + { + id = table.Column(type: "uuid", nullable: false), + label = table.Column(type: "character varying(1024)", maxLength: 1024, nullable: true), + last_granted_at = table.Column(type: "timestamp with time zone", nullable: true), + expired_at = table.Column(type: "timestamp with time zone", nullable: true), + account_id = table.Column(type: "uuid", nullable: false), + challenge_id = table.Column(type: "uuid", nullable: false), + app_id = table.Column(type: "uuid", nullable: true), + created_at = table.Column(type: "timestamp with time zone", nullable: false), + updated_at = table.Column(type: "timestamp with time zone", nullable: false), + deleted_at = table.Column(type: "timestamp with time zone", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_auth_sessions", x => x.id); + table.ForeignKey( + name: "fk_auth_sessions_accounts_account_id", + column: x => x.account_id, + principalTable: "accounts", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "fk_auth_sessions_auth_challenges_challenge_id", + column: x => x.challenge_id, + principalTable: "auth_challenges", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "fk_auth_sessions_custom_apps_app_id", + column: x => x.app_id, + principalTable: "custom_apps", + principalColumn: "id"); + }); + + migrationBuilder.CreateTable( + name: "payment_transactions", + columns: table => new + { + id = table.Column(type: "uuid", nullable: false), + currency = table.Column(type: "character varying(128)", maxLength: 128, nullable: false), + amount = table.Column(type: "numeric", nullable: false), + remarks = table.Column(type: "character varying(4096)", maxLength: 4096, nullable: true), + type = table.Column(type: "integer", nullable: false), + payer_wallet_id = table.Column(type: "uuid", nullable: true), + payee_wallet_id = table.Column(type: "uuid", nullable: true), + created_at = table.Column(type: "timestamp with time zone", nullable: false), + updated_at = table.Column(type: "timestamp with time zone", nullable: false), + deleted_at = table.Column(type: "timestamp with time zone", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_payment_transactions", x => x.id); + table.ForeignKey( + name: "fk_payment_transactions_wallets_payee_wallet_id", + column: x => x.payee_wallet_id, + principalTable: "wallets", + principalColumn: "id"); + table.ForeignKey( + name: "fk_payment_transactions_wallets_payer_wallet_id", + column: x => x.payer_wallet_id, + principalTable: "wallets", + principalColumn: "id"); + }); + + migrationBuilder.CreateTable( + name: "wallet_pockets", + columns: table => new + { + id = table.Column(type: "uuid", nullable: false), + currency = table.Column(type: "character varying(128)", maxLength: 128, nullable: false), + amount = table.Column(type: "numeric", nullable: false), + wallet_id = table.Column(type: "uuid", nullable: false), + created_at = table.Column(type: "timestamp with time zone", nullable: false), + updated_at = table.Column(type: "timestamp with time zone", nullable: false), + deleted_at = table.Column(type: "timestamp with time zone", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_wallet_pockets", x => x.id); + table.ForeignKey( + name: "fk_wallet_pockets_wallets_wallet_id", + column: x => x.wallet_id, + principalTable: "wallets", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "payment_orders", + columns: table => new + { + id = table.Column(type: "uuid", nullable: false), + status = table.Column(type: "integer", nullable: false), + currency = table.Column(type: "character varying(128)", maxLength: 128, nullable: false), + remarks = table.Column(type: "character varying(4096)", maxLength: 4096, nullable: true), + app_identifier = table.Column(type: "character varying(4096)", maxLength: 4096, nullable: true), + meta = table.Column>(type: "jsonb", nullable: true), + amount = table.Column(type: "numeric", nullable: false), + expired_at = table.Column(type: "timestamp with time zone", nullable: false), + payee_wallet_id = table.Column(type: "uuid", nullable: true), + transaction_id = table.Column(type: "uuid", nullable: true), + created_at = table.Column(type: "timestamp with time zone", nullable: false), + updated_at = table.Column(type: "timestamp with time zone", nullable: false), + deleted_at = table.Column(type: "timestamp with time zone", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_payment_orders", x => x.id); + table.ForeignKey( + name: "fk_payment_orders_payment_transactions_transaction_id", + column: x => x.transaction_id, + principalTable: "payment_transactions", + principalColumn: "id"); + table.ForeignKey( + name: "fk_payment_orders_wallets_payee_wallet_id", + column: x => x.payee_wallet_id, + principalTable: "wallets", + principalColumn: "id"); + }); + + migrationBuilder.CreateIndex( + name: "ix_abuse_reports_account_id", + table: "abuse_reports", + column: "account_id"); + + migrationBuilder.CreateIndex( + name: "ix_account_auth_factors_account_id", + table: "account_auth_factors", + column: "account_id"); + + migrationBuilder.CreateIndex( + name: "ix_account_check_in_results_account_id", + table: "account_check_in_results", + column: "account_id"); + + migrationBuilder.CreateIndex( + name: "ix_account_connections_account_id", + table: "account_connections", + column: "account_id"); + + migrationBuilder.CreateIndex( + name: "ix_account_contacts_account_id", + table: "account_contacts", + column: "account_id"); + + migrationBuilder.CreateIndex( + name: "ix_account_profiles_account_id", + table: "account_profiles", + column: "account_id", + unique: true); + + migrationBuilder.CreateIndex( + name: "ix_account_relationships_related_id", + table: "account_relationships", + column: "related_id"); + + migrationBuilder.CreateIndex( + name: "ix_account_statuses_account_id", + table: "account_statuses", + column: "account_id"); + + migrationBuilder.CreateIndex( + name: "ix_accounts_name", + table: "accounts", + column: "name", + unique: true); + + migrationBuilder.CreateIndex( + name: "ix_action_logs_account_id", + table: "action_logs", + column: "account_id"); + + migrationBuilder.CreateIndex( + name: "ix_auth_challenges_account_id", + table: "auth_challenges", + column: "account_id"); + + migrationBuilder.CreateIndex( + name: "ix_auth_sessions_account_id", + table: "auth_sessions", + column: "account_id"); + + migrationBuilder.CreateIndex( + name: "ix_auth_sessions_app_id", + table: "auth_sessions", + column: "app_id"); + + migrationBuilder.CreateIndex( + name: "ix_auth_sessions_challenge_id", + table: "auth_sessions", + column: "challenge_id"); + + migrationBuilder.CreateIndex( + name: "ix_badges_account_id", + table: "badges", + column: "account_id"); + + migrationBuilder.CreateIndex( + name: "ix_custom_app_secrets_app_id", + table: "custom_app_secrets", + column: "app_id"); + + migrationBuilder.CreateIndex( + name: "ix_magic_spells_account_id", + table: "magic_spells", + column: "account_id"); + + migrationBuilder.CreateIndex( + name: "ix_magic_spells_spell", + table: "magic_spells", + column: "spell", + unique: true); + + migrationBuilder.CreateIndex( + name: "ix_notification_push_subscriptions_account_id", + table: "notification_push_subscriptions", + column: "account_id"); + + migrationBuilder.CreateIndex( + name: "ix_notification_push_subscriptions_device_token_device_id_acco", + table: "notification_push_subscriptions", + columns: new[] { "device_token", "device_id", "account_id" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "ix_notifications_account_id", + table: "notifications", + column: "account_id"); + + migrationBuilder.CreateIndex( + name: "ix_payment_orders_payee_wallet_id", + table: "payment_orders", + column: "payee_wallet_id"); + + migrationBuilder.CreateIndex( + name: "ix_payment_orders_transaction_id", + table: "payment_orders", + column: "transaction_id"); + + migrationBuilder.CreateIndex( + name: "ix_payment_transactions_payee_wallet_id", + table: "payment_transactions", + column: "payee_wallet_id"); + + migrationBuilder.CreateIndex( + name: "ix_payment_transactions_payer_wallet_id", + table: "payment_transactions", + column: "payer_wallet_id"); + + migrationBuilder.CreateIndex( + name: "ix_permission_nodes_group_id", + table: "permission_nodes", + column: "group_id"); + + migrationBuilder.CreateIndex( + name: "ix_permission_nodes_key_area_actor", + table: "permission_nodes", + columns: new[] { "key", "area", "actor" }); + + migrationBuilder.CreateIndex( + name: "ix_wallet_pockets_wallet_id", + table: "wallet_pockets", + column: "wallet_id"); + + migrationBuilder.CreateIndex( + name: "ix_wallet_subscriptions_account_id", + table: "wallet_subscriptions", + column: "account_id"); + + migrationBuilder.CreateIndex( + name: "ix_wallet_subscriptions_coupon_id", + table: "wallet_subscriptions", + column: "coupon_id"); + + migrationBuilder.CreateIndex( + name: "ix_wallet_subscriptions_identifier", + table: "wallet_subscriptions", + column: "identifier"); + + migrationBuilder.CreateIndex( + name: "ix_wallets_account_id", + table: "wallets", + column: "account_id"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "abuse_reports"); + + migrationBuilder.DropTable( + name: "account_auth_factors"); + + migrationBuilder.DropTable( + name: "account_check_in_results"); + + migrationBuilder.DropTable( + name: "account_connections"); + + migrationBuilder.DropTable( + name: "account_contacts"); + + migrationBuilder.DropTable( + name: "account_profiles"); + + migrationBuilder.DropTable( + name: "account_relationships"); + + migrationBuilder.DropTable( + name: "account_statuses"); + + migrationBuilder.DropTable( + name: "action_logs"); + + migrationBuilder.DropTable( + name: "auth_sessions"); + + migrationBuilder.DropTable( + name: "badges"); + + migrationBuilder.DropTable( + name: "custom_app_secrets"); + + migrationBuilder.DropTable( + name: "magic_spells"); + + migrationBuilder.DropTable( + name: "notification_push_subscriptions"); + + migrationBuilder.DropTable( + name: "notifications"); + + migrationBuilder.DropTable( + name: "payment_orders"); + + migrationBuilder.DropTable( + name: "permission_group_members"); + + migrationBuilder.DropTable( + name: "permission_nodes"); + + migrationBuilder.DropTable( + name: "wallet_pockets"); + + migrationBuilder.DropTable( + name: "wallet_subscriptions"); + + migrationBuilder.DropTable( + name: "auth_challenges"); + + migrationBuilder.DropTable( + name: "custom_apps"); + + migrationBuilder.DropTable( + name: "payment_transactions"); + + migrationBuilder.DropTable( + name: "permission_groups"); + + migrationBuilder.DropTable( + name: "wallet_coupons"); + + migrationBuilder.DropTable( + name: "wallets"); + + migrationBuilder.DropTable( + name: "accounts"); + } + } +} diff --git a/DysonNetwork.Pass/Migrations/AppDatabaseModelSnapshot.cs b/DysonNetwork.Pass/Migrations/AppDatabaseModelSnapshot.cs new file mode 100644 index 0000000..cfd2d9d --- /dev/null +++ b/DysonNetwork.Pass/Migrations/AppDatabaseModelSnapshot.cs @@ -0,0 +1,1974 @@ +// +using System; +using System.Collections.Generic; +using System.Text.Json; +using DysonNetwork.Pass; +using DysonNetwork.Pass.Account; +using DysonNetwork.Pass.Developer; +using DysonNetwork.Pass.Wallet; +using DysonNetwork.Shared.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using NetTopologySuite.Geometries; +using NodaTime; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace DysonNetwork.Pass.Migrations +{ + [DbContext(typeof(AppDatabase))] + partial class AppDatabaseModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.7") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "postgis"); + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("DysonNetwork.Pass.Account.AbuseReport", 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.Pass.Account.Account", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("ActivatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("activated_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("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("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.Pass.Account.AccountAuthFactor", 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.Pass.Account.AccountBadge", 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.Pass.Account.AccountConnection", 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.Pass.Account.AccountContact", 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("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.Pass.Account.AccountProfile", 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("BackgroundId") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("background_id"); + + 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("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("PictureId") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("picture_id"); + + b.Property("Pronouns") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("pronouns"); + + 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("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.Pass.Account.ActionLog", 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("geometry") + .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.Pass.Account.CheckInResult", 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("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.Pass.Account.MagicSpell", 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.Pass.Account.Notification", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("AccountId") + .HasColumnType("uuid") + .HasColumnName("account_id"); + + b.Property("Content") + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .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>("Meta") + .HasColumnType("jsonb") + .HasColumnName("meta"); + + b.Property("Priority") + .HasColumnType("integer") + .HasColumnName("priority"); + + b.Property("Subtitle") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)") + .HasColumnName("subtitle"); + + b.Property("Title") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("title"); + + b.Property("Topic") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("topic"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.Property("ViewedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("viewed_at"); + + b.HasKey("Id") + .HasName("pk_notifications"); + + b.HasIndex("AccountId") + .HasDatabaseName("ix_notifications_account_id"); + + b.ToTable("notifications", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Pass.Account.NotificationPushSubscription", 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(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("device_id"); + + b.Property("DeviceToken") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("device_token"); + + b.Property("LastUsedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_used_at"); + + b.Property("Provider") + .HasColumnType("integer") + .HasColumnName("provider"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.HasKey("Id") + .HasName("pk_notification_push_subscriptions"); + + b.HasIndex("AccountId") + .HasDatabaseName("ix_notification_push_subscriptions_account_id"); + + b.HasIndex("DeviceToken", "DeviceId", "AccountId") + .IsUnique() + .HasDatabaseName("ix_notification_push_subscriptions_device_token_device_id_acco"); + + b.ToTable("notification_push_subscriptions", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Pass.Account.Relationship", 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.Pass.Account.Status", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("AccountId") + .HasColumnType("uuid") + .HasColumnName("account_id"); + + 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("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("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.Pass.Auth.AuthChallenge", 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") + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("device_id"); + + 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("geometry") + .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("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_challenges"); + + b.HasIndex("AccountId") + .HasDatabaseName("ix_auth_challenges_account_id"); + + b.ToTable("auth_challenges", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Pass.Auth.AuthSession", 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("ChallengeId") + .HasColumnType("uuid") + .HasColumnName("challenge_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("Label") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("label"); + + b.Property("LastGrantedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_granted_at"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.HasKey("Id") + .HasName("pk_auth_sessions"); + + b.HasIndex("AccountId") + .HasDatabaseName("ix_auth_sessions_account_id"); + + b.HasIndex("AppId") + .HasDatabaseName("ix_auth_sessions_app_id"); + + b.HasIndex("ChallengeId") + .HasDatabaseName("ix_auth_sessions_challenge_id"); + + b.ToTable("auth_sessions", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Pass.Developer.CustomApp", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("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") + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("description"); + + b.Property("Links") + .HasColumnType("jsonb") + .HasColumnName("links"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("name"); + + b.Property("OauthConfig") + .HasColumnType("jsonb") + .HasColumnName("oauth_config"); + + b.Property("Picture") + .HasColumnType("jsonb") + .HasColumnName("picture"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("slug"); + + b.Property("Status") + .HasColumnType("integer") + .HasColumnName("status"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.Property("Verification") + .HasColumnType("jsonb") + .HasColumnName("verification"); + + b.HasKey("Id") + .HasName("pk_custom_apps"); + + b.ToTable("custom_apps", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Pass.Developer.CustomAppSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("AppId") + .HasColumnType("uuid") + .HasColumnName("app_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("Description") + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("description"); + + b.Property("ExpiredAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("expired_at"); + + b.Property("IsOidc") + .HasColumnType("boolean") + .HasColumnName("is_oidc"); + + b.Property("Secret") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("secret"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.HasKey("Id") + .HasName("pk_custom_app_secrets"); + + b.HasIndex("AppId") + .HasDatabaseName("ix_custom_app_secrets_app_id"); + + b.ToTable("custom_app_secrets", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Pass.Permission.PermissionGroup", 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.Pass.Permission.PermissionGroupMember", 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.Pass.Permission.PermissionNode", 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("Area") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("area"); + + 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("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", "Area", "Actor") + .HasDatabaseName("ix_permission_nodes_key_area_actor"); + + b.ToTable("permission_nodes", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Pass.Wallet.Coupon", 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.Pass.Wallet.Order", 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("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.Pass.Wallet.Subscription", 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.ToTable("wallet_subscriptions", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Pass.Wallet.Transaction", 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.Pass.Wallet.Wallet", 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.Pass.Wallet.WalletPocket", 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.Pass.Account.AbuseReport", b => + { + b.HasOne("DysonNetwork.Pass.Account.Account", "Account") + .WithMany() + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_abuse_reports_accounts_account_id"); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("DysonNetwork.Pass.Account.AccountAuthFactor", b => + { + b.HasOne("DysonNetwork.Pass.Account.Account", "Account") + .WithMany("AuthFactors") + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_account_auth_factors_accounts_account_id"); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("DysonNetwork.Pass.Account.AccountBadge", b => + { + b.HasOne("DysonNetwork.Pass.Account.Account", "Account") + .WithMany("Badges") + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_badges_accounts_account_id"); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("DysonNetwork.Pass.Account.AccountConnection", b => + { + b.HasOne("DysonNetwork.Pass.Account.Account", "Account") + .WithMany("Connections") + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_account_connections_accounts_account_id"); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("DysonNetwork.Pass.Account.AccountContact", b => + { + b.HasOne("DysonNetwork.Pass.Account.Account", "Account") + .WithMany("Contacts") + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_account_contacts_accounts_account_id"); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("DysonNetwork.Pass.Account.AccountProfile", b => + { + b.HasOne("DysonNetwork.Pass.Account.Account", "Account") + .WithOne("Profile") + .HasForeignKey("DysonNetwork.Pass.Account.AccountProfile", "AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_account_profiles_accounts_account_id"); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("DysonNetwork.Pass.Account.ActionLog", b => + { + b.HasOne("DysonNetwork.Pass.Account.Account", "Account") + .WithMany() + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_action_logs_accounts_account_id"); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("DysonNetwork.Pass.Account.CheckInResult", b => + { + b.HasOne("DysonNetwork.Pass.Account.Account", "Account") + .WithMany() + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_account_check_in_results_accounts_account_id"); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("DysonNetwork.Pass.Account.MagicSpell", b => + { + b.HasOne("DysonNetwork.Pass.Account.Account", "Account") + .WithMany() + .HasForeignKey("AccountId") + .HasConstraintName("fk_magic_spells_accounts_account_id"); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("DysonNetwork.Pass.Account.Notification", b => + { + b.HasOne("DysonNetwork.Pass.Account.Account", "Account") + .WithMany() + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_notifications_accounts_account_id"); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("DysonNetwork.Pass.Account.NotificationPushSubscription", b => + { + b.HasOne("DysonNetwork.Pass.Account.Account", "Account") + .WithMany() + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_notification_push_subscriptions_accounts_account_id"); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("DysonNetwork.Pass.Account.Relationship", b => + { + b.HasOne("DysonNetwork.Pass.Account.Account", "Account") + .WithMany("OutgoingRelationships") + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_account_relationships_accounts_account_id"); + + b.HasOne("DysonNetwork.Pass.Account.Account", "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.Pass.Account.Status", b => + { + b.HasOne("DysonNetwork.Pass.Account.Account", "Account") + .WithMany() + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_account_statuses_accounts_account_id"); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("DysonNetwork.Pass.Auth.AuthChallenge", b => + { + b.HasOne("DysonNetwork.Pass.Account.Account", "Account") + .WithMany("Challenges") + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_auth_challenges_accounts_account_id"); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("DysonNetwork.Pass.Auth.AuthSession", b => + { + b.HasOne("DysonNetwork.Pass.Account.Account", "Account") + .WithMany("Sessions") + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_auth_sessions_accounts_account_id"); + + b.HasOne("DysonNetwork.Pass.Developer.CustomApp", "App") + .WithMany() + .HasForeignKey("AppId") + .HasConstraintName("fk_auth_sessions_custom_apps_app_id"); + + b.HasOne("DysonNetwork.Pass.Auth.AuthChallenge", "Challenge") + .WithMany() + .HasForeignKey("ChallengeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_auth_sessions_auth_challenges_challenge_id"); + + b.Navigation("Account"); + + b.Navigation("App"); + + b.Navigation("Challenge"); + }); + + modelBuilder.Entity("DysonNetwork.Pass.Developer.CustomAppSecret", b => + { + b.HasOne("DysonNetwork.Pass.Developer.CustomApp", "App") + .WithMany("Secrets") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_custom_app_secrets_custom_apps_app_id"); + + b.Navigation("App"); + }); + + modelBuilder.Entity("DysonNetwork.Pass.Permission.PermissionGroupMember", b => + { + b.HasOne("DysonNetwork.Pass.Permission.PermissionGroup", "Group") + .WithMany("Members") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_permission_group_members_permission_groups_group_id"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("DysonNetwork.Pass.Permission.PermissionNode", b => + { + b.HasOne("DysonNetwork.Pass.Permission.PermissionGroup", "Group") + .WithMany("Nodes") + .HasForeignKey("GroupId") + .HasConstraintName("fk_permission_nodes_permission_groups_group_id"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("DysonNetwork.Pass.Wallet.Order", b => + { + b.HasOne("DysonNetwork.Pass.Wallet.Wallet", "PayeeWallet") + .WithMany() + .HasForeignKey("PayeeWalletId") + .HasConstraintName("fk_payment_orders_wallets_payee_wallet_id"); + + b.HasOne("DysonNetwork.Pass.Wallet.Transaction", "Transaction") + .WithMany() + .HasForeignKey("TransactionId") + .HasConstraintName("fk_payment_orders_payment_transactions_transaction_id"); + + b.Navigation("PayeeWallet"); + + b.Navigation("Transaction"); + }); + + modelBuilder.Entity("DysonNetwork.Pass.Wallet.Subscription", b => + { + b.HasOne("DysonNetwork.Pass.Account.Account", "Account") + .WithMany() + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_wallet_subscriptions_accounts_account_id"); + + b.HasOne("DysonNetwork.Pass.Wallet.Coupon", "Coupon") + .WithMany() + .HasForeignKey("CouponId") + .HasConstraintName("fk_wallet_subscriptions_wallet_coupons_coupon_id"); + + b.Navigation("Account"); + + b.Navigation("Coupon"); + }); + + modelBuilder.Entity("DysonNetwork.Pass.Wallet.Transaction", b => + { + b.HasOne("DysonNetwork.Pass.Wallet.Wallet", "PayeeWallet") + .WithMany() + .HasForeignKey("PayeeWalletId") + .HasConstraintName("fk_payment_transactions_wallets_payee_wallet_id"); + + b.HasOne("DysonNetwork.Pass.Wallet.Wallet", "PayerWallet") + .WithMany() + .HasForeignKey("PayerWalletId") + .HasConstraintName("fk_payment_transactions_wallets_payer_wallet_id"); + + b.Navigation("PayeeWallet"); + + b.Navigation("PayerWallet"); + }); + + modelBuilder.Entity("DysonNetwork.Pass.Wallet.Wallet", b => + { + b.HasOne("DysonNetwork.Pass.Account.Account", "Account") + .WithMany() + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_wallets_accounts_account_id"); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("DysonNetwork.Pass.Wallet.WalletPocket", b => + { + b.HasOne("DysonNetwork.Pass.Wallet.Wallet", "Wallet") + .WithMany("Pockets") + .HasForeignKey("WalletId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_wallet_pockets_wallets_wallet_id"); + + b.Navigation("Wallet"); + }); + + modelBuilder.Entity("DysonNetwork.Pass.Account.Account", 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.Pass.Developer.CustomApp", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("DysonNetwork.Pass.Permission.PermissionGroup", b => + { + b.Navigation("Members"); + + b.Navigation("Nodes"); + }); + + modelBuilder.Entity("DysonNetwork.Pass.Wallet.Wallet", b => + { + b.Navigation("Pockets"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/DysonNetwork.Pass/Program.cs b/DysonNetwork.Pass/Program.cs index 831b8f6..2d11e2e 100644 --- a/DysonNetwork.Pass/Program.cs +++ b/DysonNetwork.Pass/Program.cs @@ -28,8 +28,6 @@ builder.Services.AddAppBusinessServices(builder.Configuration); // Add scheduled jobs builder.Services.AddAppScheduledJobs(); -builder.Services.AddHostedService(); - var app = builder.Build(); // Run database migrations diff --git a/DysonNetwork.Pass/Startup/ServiceRegistrationHostedService.cs b/DysonNetwork.Pass/Startup/ServiceRegistrationHostedService.cs deleted file mode 100644 index 7abe7fc..0000000 --- a/DysonNetwork.Pass/Startup/ServiceRegistrationHostedService.cs +++ /dev/null @@ -1,56 +0,0 @@ -using DysonNetwork.Shared.Registry; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; -using System.Threading; -using System.Threading.Tasks; - -namespace DysonNetwork.Pass.Startup; - -public class ServiceRegistrationHostedService : IHostedService -{ - private readonly ServiceRegistry _serviceRegistry; - private readonly IConfiguration _configuration; - private readonly ILogger _logger; - - public ServiceRegistrationHostedService( - ServiceRegistry serviceRegistry, - IConfiguration configuration, - ILogger logger) - { - _serviceRegistry = serviceRegistry; - _configuration = configuration; - _logger = logger; - } - - public async Task StartAsync(CancellationToken cancellationToken) - { - var serviceName = "DysonNetwork.Pass"; // Preset service name - var serviceUrl = _configuration["Service:Url"]; - - if (string.IsNullOrEmpty(serviceName) || string.IsNullOrEmpty(serviceUrl)) - { - _logger.LogWarning("Service name or URL not configured. Skipping Etcd registration."); - return; - } - - _logger.LogInformation("Registering service {ServiceName} at {ServiceUrl} with Etcd.", serviceName, serviceUrl); - try - { - await _serviceRegistry.RegisterService(serviceName, serviceUrl); - _logger.LogInformation("Service {ServiceName} registered successfully.", serviceName); - } - catch (Exception ex) - { - _logger.LogError(ex, "Failed to register service {ServiceName} with Etcd.", serviceName); - } - } - - public Task StopAsync(CancellationToken cancellationToken) - { - // The lease will expire automatically if the service stops. - // For explicit unregistration, you would implement it here. - _logger.LogInformation("Service registration hosted service is stopping."); - return Task.CompletedTask; - } -} diff --git a/DysonNetwork.Pass/appsettings.json b/DysonNetwork.Pass/appsettings.json index 82c1088..5a0f1e1 100644 --- a/DysonNetwork.Pass/appsettings.json +++ b/DysonNetwork.Pass/appsettings.json @@ -9,8 +9,9 @@ }, "AllowedHosts": "*", "ConnectionStrings": { - "App": "Host=localhost;Port=5432;Database=dyson_network;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60", - "FastRetrieve": "localhost:6379" + "App": "Host=localhost;Port=5432;Database=dyson_pass;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60", + "FastRetrieve": "localhost:6379", + "Etcd": "etcd.orb.local:2379" }, "Authentication": { "Schemes": { @@ -36,60 +37,11 @@ "AuthorizationCodeLifetime": "00:30:00", "RequireHttpsMetadata": true }, - "Tus": { - "StorePath": "Uploads" - }, - "Storage": { - "PreferredRemote": "minio", - "Remote": [ - { - "Id": "minio", - "Label": "Minio", - "Region": "auto", - "Bucket": "solar-network-development", - "Endpoint": "localhost:9000", - "SecretId": "littlesheep", - "SecretKey": "password", - "EnabledSigned": true, - "EnableSsl": false - }, - { - "Id": "cloudflare", - "Label": "Cloudflare R2", - "Region": "auto", - "Bucket": "solar-network", - "Endpoint": "0a70a6d1b7128888c823359d0008f4e1.r2.cloudflarestorage.com", - "SecretId": "8ff5d06c7b1639829d60bc6838a542e6", - "SecretKey": "fd58158c5201be16d1872c9209d9cf199421dae3c2f9972f94b2305976580d67", - "EnableSigned": true, - "EnableSsl": true - } - ] - }, "Captcha": { "Provider": "cloudflare", "ApiKey": "0x4AAAAAABCDUdOujj4feOb_", "ApiSecret": "0x4AAAAAABCDUWABiJQweqlB7tYq-IqIm8U" }, - "Notifications": { - "Topic": "dev.solsynth.solian", - "Endpoint": "http://localhost:8088" - }, - "Email": { - "Server": "smtp4dev.orb.local", - "Port": 25, - "UseSsl": false, - "Username": "no-reply@mail.solsynth.dev", - "Password": "password", - "FromAddress": "no-reply@mail.solsynth.dev", - "FromName": "Alphabot", - "SubjectPrefix": "Solar Network" - }, - "RealtimeChat": { - "Endpoint": "https://solar-network-im44o8gq.livekit.cloud", - "ApiKey": "APIs6TiL8wj3A4j", - "ApiSecret": "SffxRneIwTnlHPtEf3zicmmv3LUEl7xXael4PvWZrEhE" - }, "GeoIp": { "DatabasePath": "./Keys/GeoLite2-City.mmdb" }, @@ -125,5 +77,14 @@ "KnownProxies": [ "127.0.0.1", "::1" - ] + ], + "Service": { + "Name": "DysonNetwork.Pass", + "Url": "http://localhost:5216", + "ClientCert": "../Certificates/client.crt", + "ClientKey": "../Certificates/client.key" + }, + "Etcd": { + "Insecure": true + } } diff --git a/DysonNetwork.Pusher/DysonNetwork.Pusher.csproj b/DysonNetwork.Pusher/DysonNetwork.Pusher.csproj index 5bf2531..a9aedab 100644 --- a/DysonNetwork.Pusher/DysonNetwork.Pusher.csproj +++ b/DysonNetwork.Pusher/DysonNetwork.Pusher.csproj @@ -13,7 +13,11 @@ - + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + diff --git a/DysonNetwork.Pusher/Migrations/20250713122638_InitialMigration.Designer.cs b/DysonNetwork.Pusher/Migrations/20250713122638_InitialMigration.Designer.cs new file mode 100644 index 0000000..25b1168 --- /dev/null +++ b/DysonNetwork.Pusher/Migrations/20250713122638_InitialMigration.Designer.cs @@ -0,0 +1,151 @@ +// +using System; +using System.Collections.Generic; +using DysonNetwork.Pusher; +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.Pusher.Migrations +{ + [DbContext(typeof(AppDatabase))] + [Migration("20250713122638_InitialMigration")] + partial class InitialMigration + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.7") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("DysonNetwork.Pusher.Notification.Notification", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("AccountId") + .HasColumnType("uuid") + .HasColumnName("account_id"); + + b.Property("Content") + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .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>("Meta") + .HasColumnType("jsonb") + .HasColumnName("meta"); + + b.Property("Priority") + .HasColumnType("integer") + .HasColumnName("priority"); + + b.Property("Subtitle") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)") + .HasColumnName("subtitle"); + + b.Property("Title") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("title"); + + b.Property("Topic") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("topic"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.Property("ViewedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("viewed_at"); + + b.HasKey("Id") + .HasName("pk_notifications"); + + b.ToTable("notifications", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Pusher.Notification.PushSubscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("AccountId") + .HasColumnType("uuid") + .HasColumnName("account_id"); + + b.Property("CountDelivered") + .HasColumnType("integer") + .HasColumnName("count_delivered"); + + 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(8192) + .HasColumnType("character varying(8192)") + .HasColumnName("device_id"); + + b.Property("DeviceToken") + .IsRequired() + .HasMaxLength(8192) + .HasColumnType("character varying(8192)") + .HasColumnName("device_token"); + + b.Property("LastUsedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_used_at"); + + b.Property("Provider") + .HasColumnType("integer") + .HasColumnName("provider"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.HasKey("Id") + .HasName("pk_push_subscriptions"); + + b.HasIndex("AccountId", "DeviceId", "DeletedAt") + .IsUnique() + .HasDatabaseName("ix_push_subscriptions_account_id_device_id_deleted_at"); + + b.ToTable("push_subscriptions", (string)null); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/DysonNetwork.Pusher/Migrations/20250713122638_InitialMigration.cs b/DysonNetwork.Pusher/Migrations/20250713122638_InitialMigration.cs new file mode 100644 index 0000000..b5ac718 --- /dev/null +++ b/DysonNetwork.Pusher/Migrations/20250713122638_InitialMigration.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore.Migrations; +using NodaTime; + +#nullable disable + +namespace DysonNetwork.Pusher.Migrations +{ + /// + public partial class InitialMigration : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "notifications", + columns: table => new + { + id = table.Column(type: "uuid", nullable: false), + topic = table.Column(type: "character varying(1024)", maxLength: 1024, nullable: false), + title = table.Column(type: "character varying(1024)", maxLength: 1024, nullable: true), + subtitle = table.Column(type: "character varying(2048)", maxLength: 2048, nullable: true), + content = table.Column(type: "character varying(4096)", maxLength: 4096, nullable: true), + meta = table.Column>(type: "jsonb", nullable: true), + priority = table.Column(type: "integer", nullable: false), + viewed_at = table.Column(type: "timestamp with time zone", nullable: true), + account_id = table.Column(type: "uuid", nullable: false), + created_at = table.Column(type: "timestamp with time zone", nullable: false), + updated_at = table.Column(type: "timestamp with time zone", nullable: false), + deleted_at = table.Column(type: "timestamp with time zone", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_notifications", x => x.id); + }); + + migrationBuilder.CreateTable( + name: "push_subscriptions", + columns: table => new + { + id = table.Column(type: "uuid", nullable: false), + account_id = table.Column(type: "uuid", nullable: false), + device_id = table.Column(type: "character varying(8192)", maxLength: 8192, nullable: false), + device_token = table.Column(type: "character varying(8192)", maxLength: 8192, nullable: false), + provider = table.Column(type: "integer", nullable: false), + count_delivered = table.Column(type: "integer", nullable: false), + last_used_at = table.Column(type: "timestamp with time zone", nullable: true), + created_at = table.Column(type: "timestamp with time zone", nullable: false), + updated_at = table.Column(type: "timestamp with time zone", nullable: false), + deleted_at = table.Column(type: "timestamp with time zone", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_push_subscriptions", x => x.id); + }); + + migrationBuilder.CreateIndex( + name: "ix_push_subscriptions_account_id_device_id_deleted_at", + table: "push_subscriptions", + columns: new[] { "account_id", "device_id", "deleted_at" }, + unique: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "notifications"); + + migrationBuilder.DropTable( + name: "push_subscriptions"); + } + } +} diff --git a/DysonNetwork.Pusher/Migrations/AppDatabaseModelSnapshot.cs b/DysonNetwork.Pusher/Migrations/AppDatabaseModelSnapshot.cs new file mode 100644 index 0000000..bc4142b --- /dev/null +++ b/DysonNetwork.Pusher/Migrations/AppDatabaseModelSnapshot.cs @@ -0,0 +1,148 @@ +// +using System; +using System.Collections.Generic; +using DysonNetwork.Pusher; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using NodaTime; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace DysonNetwork.Pusher.Migrations +{ + [DbContext(typeof(AppDatabase))] + partial class AppDatabaseModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.7") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("DysonNetwork.Pusher.Notification.Notification", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("AccountId") + .HasColumnType("uuid") + .HasColumnName("account_id"); + + b.Property("Content") + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .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>("Meta") + .HasColumnType("jsonb") + .HasColumnName("meta"); + + b.Property("Priority") + .HasColumnType("integer") + .HasColumnName("priority"); + + b.Property("Subtitle") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)") + .HasColumnName("subtitle"); + + b.Property("Title") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("title"); + + b.Property("Topic") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("topic"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.Property("ViewedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("viewed_at"); + + b.HasKey("Id") + .HasName("pk_notifications"); + + b.ToTable("notifications", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Pusher.Notification.PushSubscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("AccountId") + .HasColumnType("uuid") + .HasColumnName("account_id"); + + b.Property("CountDelivered") + .HasColumnType("integer") + .HasColumnName("count_delivered"); + + 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(8192) + .HasColumnType("character varying(8192)") + .HasColumnName("device_id"); + + b.Property("DeviceToken") + .IsRequired() + .HasMaxLength(8192) + .HasColumnType("character varying(8192)") + .HasColumnName("device_token"); + + b.Property("LastUsedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_used_at"); + + b.Property("Provider") + .HasColumnType("integer") + .HasColumnName("provider"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.HasKey("Id") + .HasName("pk_push_subscriptions"); + + b.HasIndex("AccountId", "DeviceId", "DeletedAt") + .IsUnique() + .HasDatabaseName("ix_push_subscriptions_account_id_device_id_deleted_at"); + + b.ToTable("push_subscriptions", (string)null); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/DysonNetwork.Pusher/Notification/Notification.cs b/DysonNetwork.Pusher/Notification/Notification.cs index 3df46e5..6799022 100644 --- a/DysonNetwork.Pusher/Notification/Notification.cs +++ b/DysonNetwork.Pusher/Notification/Notification.cs @@ -19,6 +19,5 @@ public class Notification : ModelBase public Instant? ViewedAt { get; set; } public Guid AccountId { get; set; } - [JsonIgnore] public Account Account { get; set; } = null!; } diff --git a/DysonNetwork.Pass/Account/NotificationController.cs b/DysonNetwork.Pusher/Notification/NotificationController.cs similarity index 97% rename from DysonNetwork.Pass/Account/NotificationController.cs rename to DysonNetwork.Pusher/Notification/NotificationController.cs index ea2cf5f..9616b72 100644 --- a/DysonNetwork.Pass/Account/NotificationController.cs +++ b/DysonNetwork.Pusher/Notification/NotificationController.cs @@ -1,13 +1,11 @@ using System.ComponentModel.DataAnnotations; -using DysonNetwork.Pass; -using DysonNetwork.Pass.Auth; -using DysonNetwork.Pass.Permission; +using DysonNetwork.Shared.Proto; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using NodaTime; -namespace DysonNetwork.Pass.Account; +namespace DysonNetwork.Pusher.Notification; [ApiController] [Route("/api/notifications")] diff --git a/DysonNetwork.Pusher/Startup/ApplicationConfiguration.cs b/DysonNetwork.Pusher/Startup/ApplicationConfiguration.cs index 8b98dad..eacf9fa 100644 --- a/DysonNetwork.Pusher/Startup/ApplicationConfiguration.cs +++ b/DysonNetwork.Pusher/Startup/ApplicationConfiguration.cs @@ -33,8 +33,6 @@ public static class ApplicationConfiguration app.UseAuthorization(); app.MapControllers().RequireRateLimiting("fixed"); - app.MapStaticAssets().RequireRateLimiting("fixed"); - app.MapRazorPages().RequireRateLimiting("fixed"); return app; } diff --git a/DysonNetwork.Pusher/Startup/ServiceCollectionExtensions.cs b/DysonNetwork.Pusher/Startup/ServiceCollectionExtensions.cs index bbcfe8e..2148160 100644 --- a/DysonNetwork.Pusher/Startup/ServiceCollectionExtensions.cs +++ b/DysonNetwork.Pusher/Startup/ServiceCollectionExtensions.cs @@ -1,11 +1,10 @@ using System.Text.Json; using System.Threading.RateLimiting; -using dotnet_etcd.interfaces; +using DysonNetwork.Pusher.Connection; using DysonNetwork.Pusher.Email; using DysonNetwork.Pusher.Notification; using DysonNetwork.Pusher.Services; using DysonNetwork.Shared.Cache; -using DysonNetwork.Shared.Proto; using Microsoft.AspNetCore.RateLimiting; using Microsoft.OpenApi.Models; using NodaTime; @@ -44,18 +43,6 @@ public static class ServiceCollectionExtensions // Register gRPC services services.AddScoped(); - // Register AuthService.AuthServiceClient for AuthMiddleware - services.AddSingleton(sp => - { - var etcdClient = sp.GetRequiredService(); - var configuration = sp.GetRequiredService(); - var clientCertPath = configuration["ClientCert:Path"]; - var clientKeyPath = configuration["ClientKey:Path"]; - var clientCertPassword = configuration["ClientCert:Password"]; - - return GrpcClientHelper.CreateAuthServiceClient(etcdClient, clientCertPath, clientKeyPath, clientCertPassword); - }); - // Register OIDC services services.AddControllers().AddJsonOptions(options => { @@ -144,6 +131,7 @@ public static class ServiceCollectionExtensions public static IServiceCollection AddAppBusinessServices(this IServiceCollection services) { + services.AddScoped(); services.AddScoped(); services.AddScoped(); diff --git a/DysonNetwork.Pusher/Startup/ServiceRegistrationHostedService.cs b/DysonNetwork.Pusher/Startup/ServiceRegistrationHostedService.cs deleted file mode 100644 index c68d3fb..0000000 --- a/DysonNetwork.Pusher/Startup/ServiceRegistrationHostedService.cs +++ /dev/null @@ -1,56 +0,0 @@ -using DysonNetwork.Shared.Registry; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; -using System.Threading; -using System.Threading.Tasks; - -namespace DysonNetwork.Pusher.Startup; - -public class ServiceRegistrationHostedService : IHostedService -{ - private readonly ServiceRegistry _serviceRegistry; - private readonly IConfiguration _configuration; - private readonly ILogger _logger; - - public ServiceRegistrationHostedService( - ServiceRegistry serviceRegistry, - IConfiguration configuration, - ILogger logger) - { - _serviceRegistry = serviceRegistry; - _configuration = configuration; - _logger = logger; - } - - public async Task StartAsync(CancellationToken cancellationToken) - { - var serviceName = "DysonNetwork.Pusher"; // Preset service name - var serviceUrl = _configuration["Service:Url"]; - - if (string.IsNullOrEmpty(serviceUrl)) - { - _logger.LogWarning("Service URL not configured. Skipping Etcd registration."); - return; - } - - _logger.LogInformation("Registering service {ServiceName} at {ServiceUrl} with Etcd.", serviceName, serviceUrl); - try - { - await _serviceRegistry.RegisterService(serviceName, serviceUrl); - _logger.LogInformation("Service {ServiceName} registered successfully.", serviceName); - } - catch (Exception ex) - { - _logger.LogError(ex, "Failed to register service {ServiceName} with Etcd.", serviceName); - } - } - - public Task StopAsync(CancellationToken cancellationToken) - { - // The lease will expire automatically if the service stops. - // For explicit unregistration, you would implement it here. - _logger.LogInformation("Service registration hosted service is stopping."); - return Task.CompletedTask; - } -} diff --git a/DysonNetwork.Pusher/appsettings.json b/DysonNetwork.Pusher/appsettings.json index 82c1088..2bea1db 100644 --- a/DysonNetwork.Pusher/appsettings.json +++ b/DysonNetwork.Pusher/appsettings.json @@ -1,6 +1,6 @@ { "Debug": true, - "BaseUrl": "http://localhost:5071", + "BaseUrl": "http://localhost:5212", "Logging": { "LogLevel": { "Default": "Information", @@ -9,67 +9,9 @@ }, "AllowedHosts": "*", "ConnectionStrings": { - "App": "Host=localhost;Port=5432;Database=dyson_network;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60", - "FastRetrieve": "localhost:6379" - }, - "Authentication": { - "Schemes": { - "Bearer": { - "ValidAudiences": [ - "http://localhost:5071", - "https://localhost:7099" - ], - "ValidIssuer": "solar-network" - } - } - }, - "AuthToken": { - "PublicKeyPath": "Keys/PublicKey.pem", - "PrivateKeyPath": "Keys/PrivateKey.pem" - }, - "OidcProvider": { - "IssuerUri": "https://nt.solian.app", - "PublicKeyPath": "Keys/PublicKey.pem", - "PrivateKeyPath": "Keys/PrivateKey.pem", - "AccessTokenLifetime": "01:00:00", - "RefreshTokenLifetime": "30.00:00:00", - "AuthorizationCodeLifetime": "00:30:00", - "RequireHttpsMetadata": true - }, - "Tus": { - "StorePath": "Uploads" - }, - "Storage": { - "PreferredRemote": "minio", - "Remote": [ - { - "Id": "minio", - "Label": "Minio", - "Region": "auto", - "Bucket": "solar-network-development", - "Endpoint": "localhost:9000", - "SecretId": "littlesheep", - "SecretKey": "password", - "EnabledSigned": true, - "EnableSsl": false - }, - { - "Id": "cloudflare", - "Label": "Cloudflare R2", - "Region": "auto", - "Bucket": "solar-network", - "Endpoint": "0a70a6d1b7128888c823359d0008f4e1.r2.cloudflarestorage.com", - "SecretId": "8ff5d06c7b1639829d60bc6838a542e6", - "SecretKey": "fd58158c5201be16d1872c9209d9cf199421dae3c2f9972f94b2305976580d67", - "EnableSigned": true, - "EnableSsl": true - } - ] - }, - "Captcha": { - "Provider": "cloudflare", - "ApiKey": "0x4AAAAAABCDUdOujj4feOb_", - "ApiSecret": "0x4AAAAAABCDUWABiJQweqlB7tYq-IqIm8U" + "App": "Host=localhost;Port=5432;Database=dyson_pusher;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60", + "FastRetrieve": "localhost:6379", + "Etcd": "etcd.orb.local:2379" }, "Notifications": { "Topic": "dev.solsynth.solian", @@ -85,45 +27,20 @@ "FromName": "Alphabot", "SubjectPrefix": "Solar Network" }, - "RealtimeChat": { - "Endpoint": "https://solar-network-im44o8gq.livekit.cloud", - "ApiKey": "APIs6TiL8wj3A4j", - "ApiSecret": "SffxRneIwTnlHPtEf3zicmmv3LUEl7xXael4PvWZrEhE" - }, "GeoIp": { "DatabasePath": "./Keys/GeoLite2-City.mmdb" }, - "Oidc": { - "Google": { - "ClientId": "961776991058-963m1qin2vtp8fv693b5fdrab5hmpl89.apps.googleusercontent.com", - "ClientSecret": "" - }, - "Apple": { - "ClientId": "dev.solsynth.solian", - "TeamId": "W7HPZ53V6B", - "KeyId": "B668YP4KBG", - "PrivateKeyPath": "./Keys/Solarpass.p8" - }, - "Microsoft": { - "ClientId": "YOUR_MICROSOFT_CLIENT_ID", - "ClientSecret": "YOUR_MICROSOFT_CLIENT_SECRET", - "DiscoveryEndpoint": "YOUR_MICROSOFT_DISCOVERY_ENDPOINT" - } - }, - "Payment": { - "Auth": { - "Afdian": "" - }, - "Subscriptions": { - "Afdian": { - "7d17aae23c9611f0b5705254001e7c00": "solian.stellar.primary", - "7dfae4743c9611f0b3a55254001e7c00": "solian.stellar.nova", - "141713ee3d6211f085b352540025c377": "solian.stellar.supernova" - } - } - }, "KnownProxies": [ "127.0.0.1", "::1" - ] + ], + "Service": { + "Name": "DysonNetwork.Pusher", + "Url": "http://localhost:5212", + "ClientCert": "../Certificates/client.crt", + "ClientKey": "../Certificates/client.key" + }, + "Etcd": { + "Insecure": true + } } diff --git a/DysonNetwork.Shared/Auth/Startup.cs b/DysonNetwork.Shared/Auth/Startup.cs index b5894fb..6f29d03 100644 --- a/DysonNetwork.Shared/Auth/Startup.cs +++ b/DysonNetwork.Shared/Auth/Startup.cs @@ -12,7 +12,7 @@ public static class DysonAuthStartup IConfiguration configuration ) { - services.AddSingleton(sp => + services.AddSingleton(sp => { var etcdClient = sp.GetRequiredService(); var config = sp.GetRequiredService(); @@ -20,9 +20,12 @@ public static class DysonAuthStartup var clientKeyPath = config["ClientKey:Path"]; var clientCertPassword = config["ClientCert:Password"]; - return GrpcClientHelper.CreateAuthServiceClient(etcdClient, clientCertPath, clientKeyPath, clientCertPassword); + return GrpcClientHelper + .CreateAuthServiceClient(etcdClient, clientCertPath, clientKeyPath, clientCertPassword) + .GetAwaiter() + .GetResult(); }); - + services.AddAuthentication(options => { options.DefaultAuthenticateScheme = AuthConstants.SchemeName; diff --git a/DysonNetwork.Shared/Registry/RegistryHostedService.cs b/DysonNetwork.Shared/Registry/RegistryHostedService.cs index da7d72d..a1d8383 100644 --- a/DysonNetwork.Shared/Registry/RegistryHostedService.cs +++ b/DysonNetwork.Shared/Registry/RegistryHostedService.cs @@ -11,10 +11,17 @@ public class RegistryHostedService( ) : IHostedService { + private CancellationTokenSource? _cts; + public async Task StartAsync(CancellationToken cancellationToken) { var serviceName = configuration["Service:Name"]; var serviceUrl = configuration["Service:Url"]; + var insecure = configuration.GetValue("Etcd:Insecure"); + var remote = configuration.GetConnectionString("Etcd"); + + if (insecure) + logger.LogWarning("Etcd is configured to use insecure channel."); if (string.IsNullOrEmpty(serviceUrl) || string.IsNullOrEmpty(serviceName)) { @@ -22,10 +29,16 @@ public class RegistryHostedService( return; } - logger.LogInformation("Registering service {ServiceName} at {ServiceUrl} with Etcd.", serviceName, serviceUrl); + logger.LogInformation( + "Registering service {ServiceName} at {ServiceUrl} with Etcd ({Remote}).", + serviceName, + serviceUrl, + remote + ); try { - await serviceRegistry.RegisterService(serviceName, serviceUrl); + _cts = new CancellationTokenSource(); + await serviceRegistry.RegisterService(serviceName, serviceUrl, cancellationToken: _cts.Token); logger.LogInformation("Service {ServiceName} registered successfully.", serviceName); } catch (Exception ex) @@ -36,6 +49,8 @@ public class RegistryHostedService( public async Task StopAsync(CancellationToken cancellationToken) { + _cts?.Cancel(); + // The lease will expire automatically if the service stops ungracefully. var serviceName = configuration["Service:Name"]; if (serviceName is not null) diff --git a/DysonNetwork.Shared/Registry/ServiceRegistry.cs b/DysonNetwork.Shared/Registry/ServiceRegistry.cs index 8d3e7f4..2266f8b 100644 --- a/DysonNetwork.Shared/Registry/ServiceRegistry.cs +++ b/DysonNetwork.Shared/Registry/ServiceRegistry.cs @@ -2,12 +2,13 @@ using System.Text; using dotnet_etcd.interfaces; using Etcdserverpb; using Google.Protobuf; +using Microsoft.Extensions.Logging; namespace DysonNetwork.Shared.Registry; -public class ServiceRegistry(IEtcdClient etcd) +public class ServiceRegistry(IEtcdClient etcd, ILogger logger) { - public async Task RegisterService(string serviceName, string serviceUrl, long leaseTtlSeconds = 60) + public async Task RegisterService(string serviceName, string serviceUrl, long leaseTtlSeconds = 60, CancellationToken cancellationToken = default) { var key = $"/services/{serviceName}"; var leaseResponse = await etcd.LeaseGrantAsync(new LeaseGrantRequest { TTL = leaseTtlSeconds }); @@ -17,7 +18,18 @@ public class ServiceRegistry(IEtcdClient etcd) Value = ByteString.CopyFrom(serviceUrl, Encoding.UTF8), Lease = leaseResponse.ID }); - await etcd.LeaseKeepAlive(leaseResponse.ID, CancellationToken.None); + + _ = Task.Run(async () => + { + try + { + await etcd.LeaseKeepAlive(leaseResponse.ID, cancellationToken); + } + catch (Exception ex) + { + logger.LogError($"Lease keep-alive failed: {ex.Message}"); + } + }, cancellationToken); } public async Task UnregisterService(string serviceName) diff --git a/DysonNetwork.Sphere/appsettings.json b/DysonNetwork.Sphere/appsettings.json index 5cb458b..2437c68 100644 --- a/DysonNetwork.Sphere/appsettings.json +++ b/DysonNetwork.Sphere/appsettings.json @@ -11,7 +11,7 @@ "ConnectionStrings": { "App": "Host=localhost;Port=5432;Database=dyson_network;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60", "FastRetrieve": "localhost:6379", - "Etcd": "localhost:2379" + "Etcd": "etcd.orb.local:2379" }, "Authentication": { "Schemes": { From cde55eb237154f59089ed265175e69ae9470ce50 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Sun, 13 Jul 2025 23:38:57 +0800 Subject: [PATCH 10/42] :recycle: Still don't know what I am doing --- DysonNetwork.Pass/Account/AccountService.cs | 29 ++++---- .../Account/AccountServiceGrpc.cs | 18 +++++ DysonNetwork.Pass/Email/EmailService.cs | 11 +-- .../Permission/PermissionMiddleware.cs | 2 +- .../Startup/ServiceCollectionExtensions.cs | 7 +- DysonNetwork.Pass/Wallet/PaymentService.cs | 26 ++++--- .../Wallet/SubscriptionService.cs | 27 ++++--- .../Connection/WebSocketService.cs | 31 ++------ .../Notification/NotificationController.cs | 71 ++++++++---------- .../Notification/PushService.cs | 4 +- .../Services/PusherServiceGrpc.cs | 28 ++++++-- DysonNetwork.Shared/Auth/AuthScheme.cs | 3 +- .../Auth/PermissionMiddleware.cs | 72 +++++++++++++++++++ DysonNetwork.Shared/Auth/Startup.cs | 6 +- .../DysonNetwork.Shared.csproj | 3 +- DysonNetwork.Shared/Proto/GrpcClientHelper.cs | 39 +--------- DysonNetwork.Shared/Proto/account.proto | 9 +++ DysonNetwork.Shared/Proto/pusher.proto | 21 ++++++ DysonNetwork.Shared/Registry/ServiceHelper.cs | 47 ++++++++++++ .../Registry/ServiceRegistry.cs | 10 ++- .../Account/NotificationService.cs | 1 - .../DysonNetwork.Sphere.csproj | 2 +- DysonNetwork.sln.DotSettings.user | 3 + 23 files changed, 300 insertions(+), 170 deletions(-) create mode 100644 DysonNetwork.Shared/Auth/PermissionMiddleware.cs create mode 100644 DysonNetwork.Shared/Registry/ServiceHelper.cs diff --git a/DysonNetwork.Pass/Account/AccountService.cs b/DysonNetwork.Pass/Account/AccountService.cs index 5aea9c6..d8ff6b6 100644 --- a/DysonNetwork.Pass/Account/AccountService.cs +++ b/DysonNetwork.Pass/Account/AccountService.cs @@ -402,16 +402,17 @@ public class AccountService( return; } - await mailer.SendTemplatedEmailAsync( - account.Nick, - contact.Content, - localizer["VerificationEmail"], - new VerificationEmailModel - { - Name = account.Name, - Code = code - } - ); + await mailer + .SendTemplatedEmailAsync( + account.Nick, + contact.Content, + localizer["VerificationEmail"], + new VerificationEmailModel + { + Name = account.Name, + Code = code + } + ); await _SetFactorCode(factor, code, TimeSpan.FromMinutes(30)); break; @@ -496,7 +497,10 @@ public class AccountService( .ToListAsync(); if (session.Challenge.DeviceId is not null) - await pusher.UnsubscribePushNotifications(session.Challenge.DeviceId); + await pusher.UnsubscribePushNotificationsAsync(new UnsubscribePushNotificationsRequest() + { + DeviceId = session.Challenge.DeviceId + }); // The current session should be included in the sessions' list await db.AuthSessions @@ -655,7 +659,8 @@ public class AccountService( if (missingId.Count != 0) { - var newProfiles = missingId.Select(id => new AccountProfile { Id = Guid.NewGuid(), AccountId = id }).ToList(); + var newProfiles = missingId.Select(id => new AccountProfile { Id = Guid.NewGuid(), AccountId = id }) + .ToList(); await db.BulkInsertAsync(newProfiles); } } diff --git a/DysonNetwork.Pass/Account/AccountServiceGrpc.cs b/DysonNetwork.Pass/Account/AccountServiceGrpc.cs index 7450de1..d79d8ac 100644 --- a/DysonNetwork.Pass/Account/AccountServiceGrpc.cs +++ b/DysonNetwork.Pass/Account/AccountServiceGrpc.cs @@ -36,6 +36,24 @@ public class AccountServiceGrpc( return account.ToProtoValue(); } + public override async Task GetAccountBatch(GetAccountBatchRequest request, ServerCallContext context) + { + var accountIds = request.Id + .Select(id => Guid.TryParse(id, out var accountId) ? accountId : (Guid?)null) + .Where(id => id.HasValue) + .Select(id => id!.Value) + .ToList(); + + var accounts = await _db.Accounts + .AsNoTracking() + .Where(a => accountIds.Contains(a.Id)) + .ToListAsync(); + + var response = new GetAccountBatchResponse(); + response.Accounts.AddRange(accounts.Select(a => a.ToProtoValue())); + return response; + } + public override async Task CreateAccount(CreateAccountRequest request, ServerCallContext context) { diff --git a/DysonNetwork.Pass/Email/EmailService.cs b/DysonNetwork.Pass/Email/EmailService.cs index 7cf34ee..6384a1a 100644 --- a/DysonNetwork.Pass/Email/EmailService.cs +++ b/DysonNetwork.Pass/Email/EmailService.cs @@ -6,18 +6,11 @@ using Microsoft.AspNetCore.Components; namespace DysonNetwork.Pass.Email; public class EmailService( - IEtcdClient etcd, + PusherService.PusherServiceClient pusher, RazorViewRenderer viewRenderer, - IConfiguration configuration, ILogger logger ) { - private readonly PusherService.PusherServiceClient _client = GrpcClientHelper.CreatePusherServiceClient( - etcd, - configuration["Service:CertPath"]!, - configuration["Service:KeyPath"]! - ).GetAwaiter().GetResult(); - public async Task SendEmailAsync( string? recipientName, string recipientEmail, @@ -27,7 +20,7 @@ public class EmailService( { subject = $"[Solarpass] {subject}"; - await _client.SendEmailAsync( + await pusher.SendEmailAsync( new SendEmailRequest() { Email = new EmailMessage() diff --git a/DysonNetwork.Pass/Permission/PermissionMiddleware.cs b/DysonNetwork.Pass/Permission/PermissionMiddleware.cs index e1011fc..4764c06 100644 --- a/DysonNetwork.Pass/Permission/PermissionMiddleware.cs +++ b/DysonNetwork.Pass/Permission/PermissionMiddleware.cs @@ -2,7 +2,7 @@ namespace DysonNetwork.Pass.Permission; using System; -[AttributeUsage(AttributeTargets.Method, Inherited = true)] +[AttributeUsage(AttributeTargets.Method)] public class RequiredPermissionAttribute(string area, string key) : Attribute { public string Area { get; set; } = area; diff --git a/DysonNetwork.Pass/Startup/ServiceCollectionExtensions.cs b/DysonNetwork.Pass/Startup/ServiceCollectionExtensions.cs index af8e0f9..3f539e9 100644 --- a/DysonNetwork.Pass/Startup/ServiceCollectionExtensions.cs +++ b/DysonNetwork.Pass/Startup/ServiceCollectionExtensions.cs @@ -19,6 +19,7 @@ using DysonNetwork.Pass.Handlers; using DysonNetwork.Pass.Wallet.PaymentHandlers; using DysonNetwork.Shared.Cache; using DysonNetwork.Shared.GeoIp; +using DysonNetwork.Shared.Registry; namespace DysonNetwork.Pass.Startup; @@ -48,9 +49,8 @@ public static class ServiceCollectionExtensions options.MaxSendMessageSize = 16 * 1024 * 1024; // 16MB }); - // Register gRPC reflection for service discovery - services.AddGrpc(); - + services.AddPusherService(); + // Register gRPC services services.AddScoped(); services.AddScoped(); @@ -194,7 +194,6 @@ public static class ServiceCollectionExtensions services.AddScoped(); services.AddScoped(); services.AddScoped(); - services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); diff --git a/DysonNetwork.Pass/Wallet/PaymentService.cs b/DysonNetwork.Pass/Wallet/PaymentService.cs index c01cd62..abaf064 100644 --- a/DysonNetwork.Pass/Wallet/PaymentService.cs +++ b/DysonNetwork.Pass/Wallet/PaymentService.cs @@ -1,17 +1,18 @@ using System.Globalization; -using DysonNetwork.Pass.Account; using DysonNetwork.Pass.Localization; +using DysonNetwork.Shared.Proto; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Storage; using Microsoft.Extensions.Localization; using NodaTime; +using AccountService = DysonNetwork.Pass.Account.AccountService; namespace DysonNetwork.Pass.Wallet; public class PaymentService( AppDatabase db, WalletService wat, - NotificationService nty, + PusherService.PusherServiceClient pusher, IStringLocalizer localizer ) { @@ -205,16 +206,19 @@ public class PaymentService( var readableOrderId = order.Id.ToString().Replace("-", "")[..8]; var readableOrderRemark = order.Remarks ?? $"#{readableOrderId}"; - await nty.SendNotification( - account, - "wallets.orders.paid", - localizer["OrderPaidTitle", $"#{readableOrderId}"], - null, - localizer["OrderPaidBody", order.Amount.ToString(CultureInfo.InvariantCulture), order.Currency, - readableOrderRemark], - new Dictionary() + + await pusher.SendPushNotificationToUserAsync( + new SendPushNotificationToUserRequest { - ["order_id"] = order.Id.ToString() + UserId = account.Id.ToString(), + Notification = new PushNotification + { + Topic = "wallets.orders.paid", + Title = localizer["OrderPaidTitle", $"#{readableOrderId}"], + Body = localizer["OrderPaidBody", order.Amount.ToString(CultureInfo.InvariantCulture), order.Currency, + readableOrderRemark], + IsSavable = false + } } ); } diff --git a/DysonNetwork.Pass/Wallet/SubscriptionService.cs b/DysonNetwork.Pass/Wallet/SubscriptionService.cs index b999d0f..f6e625e 100644 --- a/DysonNetwork.Pass/Wallet/SubscriptionService.cs +++ b/DysonNetwork.Pass/Wallet/SubscriptionService.cs @@ -1,11 +1,14 @@ using System.Text.Json; -using DysonNetwork.Pass.Account; using DysonNetwork.Pass.Localization; using DysonNetwork.Pass.Wallet.PaymentHandlers; using DysonNetwork.Shared.Cache; +using DysonNetwork.Shared.Proto; +using Google.Protobuf.WellKnownTypes; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Localization; using NodaTime; +using AccountService = DysonNetwork.Pass.Account.AccountService; +using Duration = NodaTime.Duration; namespace DysonNetwork.Pass.Wallet; @@ -13,7 +16,7 @@ public class SubscriptionService( AppDatabase db, PaymentService payment, AccountService accounts, - NotificationService nty, + PusherService.PusherServiceClient pusher, IStringLocalizer localizer, IConfiguration configuration, ICacheService cache, @@ -352,15 +355,19 @@ public class SubscriptionService( ? subscription.EndedAt.Value.Minus(subscription.BegunAt).Days.ToString() : "infinite"; - await nty.SendNotification( - account, - "subscriptions.begun", - localizer["SubscriptionAppliedTitle", humanReadableName], - null, - localizer["SubscriptionAppliedBody", duration, humanReadableName], - new Dictionary() + var notification = new PushNotification + { + Topic = "subscriptions.begun", + Title = localizer["SubscriptionAppliedTitle", humanReadableName], + Body = localizer["SubscriptionAppliedBody", duration, humanReadableName], + IsSavable = false, + }; + notification.Meta.Add("subscription_id", Value.ForString(subscription.Id.ToString())); + await pusher.SendPushNotificationToUserAsync( + new SendPushNotificationToUserRequest { - ["subscription_id"] = subscription.Id.ToString(), + UserId = account.Id.ToString(), + Notification = notification } ); } diff --git a/DysonNetwork.Pusher/Connection/WebSocketService.cs b/DysonNetwork.Pusher/Connection/WebSocketService.cs index 9022a3a..c9d9af9 100644 --- a/DysonNetwork.Pusher/Connection/WebSocketService.cs +++ b/DysonNetwork.Pusher/Connection/WebSocketService.cs @@ -20,31 +20,6 @@ public class WebSocketService private static readonly ConcurrentDictionary ActiveSubscriptions = new(); // deviceId -> chatRoomId - public void SubscribeToChatRoom(string chatRoomId, string deviceId) - { - ActiveSubscriptions[deviceId] = chatRoomId; - } - - public void UnsubscribeFromChatRoom(string deviceId) - { - ActiveSubscriptions.TryRemove(deviceId, out _); - } - - public bool IsUserSubscribedToChatRoom(string accountId, string chatRoomId) - { - var userDeviceIds = ActiveConnections.Keys.Where(k => k.AccountId == accountId).Select(k => k.DeviceId); - foreach (var deviceId in userDeviceIds) - { - if (ActiveSubscriptions.TryGetValue(deviceId, out var subscribedChatRoomId) && - subscribedChatRoomId == chatRoomId) - { - return true; - } - } - - return false; - } - public bool TryAdd( (string AccountId, string DeviceId) key, WebSocket socket, @@ -67,7 +42,11 @@ public class WebSocketService ); data.Cts.Cancel(); ActiveConnections.TryRemove(key, out _); - UnsubscribeFromChatRoom(key.DeviceId); + } + + public bool GetDeviceIsConnected(string deviceId) + { + return ActiveConnections.Any(c => c.Key.DeviceId == deviceId); } public bool GetAccountIsConnected(string accountId) diff --git a/DysonNetwork.Pusher/Notification/NotificationController.cs b/DysonNetwork.Pusher/Notification/NotificationController.cs index 9616b72..f8b240e 100644 --- a/DysonNetwork.Pusher/Notification/NotificationController.cs +++ b/DysonNetwork.Pusher/Notification/NotificationController.cs @@ -1,15 +1,20 @@ using System.ComponentModel.DataAnnotations; +using DysonNetwork.Shared.Auth; using DysonNetwork.Shared.Proto; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using NodaTime; +using AccountService = DysonNetwork.Shared.Proto.AccountService; namespace DysonNetwork.Pusher.Notification; [ApiController] [Route("/api/notifications")] -public class NotificationController(AppDatabase db, NotificationService nty) : ControllerBase +public class NotificationController( + AppDatabase db, + PushService nty, + AccountService.AccountServiceClient accounts) : ControllerBase { [HttpGet("count")] [Authorize] @@ -17,9 +22,10 @@ public class NotificationController(AppDatabase db, NotificationService nty) : C { HttpContext.Items.TryGetValue("CurrentUser", out var currentUserValue); if (currentUserValue is not Account currentUser) return Unauthorized(); + var accountId = Guid.Parse(currentUser.Id); var count = await db.Notifications - .Where(s => s.AccountId == currentUser.Id && s.ViewedAt == null) + .Where(s => s.AccountId == accountId && s.ViewedAt == null) .CountAsync(); return Ok(count); } @@ -30,24 +36,25 @@ public class NotificationController(AppDatabase db, NotificationService nty) : C [FromQuery] int offset = 0, // The page size set to 5 is to avoid the client pulled the notification // but didn't render it in the screen-viewable region. - [FromQuery] int take = 5 + [FromQuery] int take = 8 ) { HttpContext.Items.TryGetValue("CurrentUser", out var currentUserValue); if (currentUserValue is not Account currentUser) return Unauthorized(); + var accountId = Guid.Parse(currentUser.Id); var totalCount = await db.Notifications - .Where(s => s.AccountId == currentUser.Id) + .Where(s => s.AccountId == accountId) .CountAsync(); var notifications = await db.Notifications - .Where(s => s.AccountId == currentUser.Id) + .Where(s => s.AccountId == accountId) .OrderByDescending(e => e.CreatedAt) .Skip(offset) .Take(take) .ToListAsync(); Response.Headers["X-Total"] = totalCount.ToString(); - await nty.MarkNotificationsViewed(notifications); + await nty.MarkNotificationsViewed(notifications.ToList()); return Ok(notifications); } @@ -55,14 +62,15 @@ public class NotificationController(AppDatabase db, NotificationService nty) : C public class PushNotificationSubscribeRequest { [MaxLength(4096)] public string DeviceToken { get; set; } = null!; - public NotificationPushProvider Provider { get; set; } + public PushProvider Provider { get; set; } } [HttpPut("subscription")] [Authorize] - public async Task> SubscribeToPushNotification( - [FromBody] PushNotificationSubscribeRequest request - ) + public async Task> + SubscribeToPushNotification( + [FromBody] PushNotificationSubscribeRequest request + ) { HttpContext.Items.TryGetValue("CurrentSession", out var currentSessionValue); HttpContext.Items.TryGetValue("CurrentUser", out var currentUserValue); @@ -72,8 +80,12 @@ public class NotificationController(AppDatabase db, NotificationService nty) : C if (currentSession == null) return Unauthorized(); var result = - await nty.SubscribePushNotification(currentUser, request.Provider, currentSession.Challenge.DeviceId!, - request.DeviceToken); + await nty.SubscribeDevice( + currentSession.Challenge.DeviceId!, + request.DeviceToken, + request.Provider, + currentUser + ); return Ok(result); } @@ -88,10 +100,11 @@ public class NotificationController(AppDatabase db, NotificationService nty) : C if (currentUser == null) return Unauthorized(); var currentSession = currentSessionValue as AuthSession; if (currentSession == null) return Unauthorized(); + var accountId = Guid.Parse(currentUser.Id); - var affectedRows = await db.NotificationPushSubscriptions + var affectedRows = await db.PushSubscriptions .Where(s => - s.AccountId == currentUser.Id && + s.AccountId == accountId && s.DeviceId == currentSession.Challenge.DeviceId ).ExecuteDeleteAsync(); return Ok(affectedRows); @@ -107,36 +120,11 @@ public class NotificationController(AppDatabase db, NotificationService nty) : C public int Priority { get; set; } = 10; } - [HttpPost("broadcast")] - [Authorize] - [RequiredPermission("global", "notifications.broadcast")] - public async Task BroadcastNotification( - [FromBody] NotificationRequest request, - [FromQuery] bool save = false - ) - { - await nty.BroadcastNotification( - new Notification - { - CreatedAt = SystemClock.Instance.GetCurrentInstant(), - UpdatedAt = SystemClock.Instance.GetCurrentInstant(), - Topic = request.Topic, - Title = request.Title, - Subtitle = request.Subtitle, - Content = request.Content, - Meta = request.Meta, - Priority = request.Priority, - }, - save - ); - return Ok(); - } - public class NotificationWithAimRequest : NotificationRequest { [Required] public List AccountId { get; set; } = null!; } - + [HttpPost("send")] [Authorize] [RequiredPermission("global", "notifications.send")] @@ -145,7 +133,6 @@ public class NotificationController(AppDatabase db, NotificationService nty) : C [FromQuery] bool save = false ) { - var accounts = await db.Accounts.Where(a => request.AccountId.Contains(a.Id)).ToListAsync(); await nty.SendNotificationBatch( new Notification { @@ -157,7 +144,7 @@ public class NotificationController(AppDatabase db, NotificationService nty) : C Content = request.Content, Meta = request.Meta, }, - accounts, + request.AccountId, save ); return Ok(); diff --git a/DysonNetwork.Pusher/Notification/PushService.cs b/DysonNetwork.Pusher/Notification/PushService.cs index 050107e..5a859e0 100644 --- a/DysonNetwork.Pusher/Notification/PushService.cs +++ b/DysonNetwork.Pusher/Notification/PushService.cs @@ -12,14 +12,14 @@ public class PushService(IConfiguration config, AppDatabase db, IHttpClientFacto private readonly string _notifyTopic = config["Notifications:Topic"]!; private readonly Uri _notifyEndpoint = new(config["Notifications:Endpoint"]!); - public async Task UnsubscribePushNotifications(string deviceId) + public async Task UnsubscribeDevice(string deviceId) { await db.PushSubscriptions .Where(s => s.DeviceId == deviceId) .ExecuteDeleteAsync(); } - public async Task SubscribePushNotification( + public async Task SubscribeDevice( string deviceId, string deviceToken, PushProvider provider, diff --git a/DysonNetwork.Pusher/Services/PusherServiceGrpc.cs b/DysonNetwork.Pusher/Services/PusherServiceGrpc.cs index a4c87ef..0ff7d05 100644 --- a/DysonNetwork.Pusher/Services/PusherServiceGrpc.cs +++ b/DysonNetwork.Pusher/Services/PusherServiceGrpc.cs @@ -9,7 +9,7 @@ namespace DysonNetwork.Pusher.Services; public class PusherServiceGrpc( EmailService emailService, - WebSocketService webSocketService, + WebSocketService websocket, PushService pushService ) : PusherService.PusherServiceBase { @@ -32,7 +32,7 @@ public class PusherServiceGrpc( Data = request.Packet.Data, ErrorMessage = request.Packet.ErrorMessage }; - webSocketService.SendPacketToAccount(request.UserId, packet); + websocket.SendPacketToAccount(request.UserId, packet); return Task.FromResult(new Empty()); } @@ -46,7 +46,7 @@ public class PusherServiceGrpc( ErrorMessage = request.Packet.ErrorMessage }; foreach (var userId in request.UserIds) - webSocketService.SendPacketToAccount(userId, packet); + websocket.SendPacketToAccount(userId, packet); return Task.FromResult(new Empty()); } @@ -60,7 +60,7 @@ public class PusherServiceGrpc( Data = request.Packet.Data, ErrorMessage = request.Packet.ErrorMessage }; - webSocketService.SendPacketToDevice(request.DeviceId, packet); + websocket.SendPacketToDevice(request.DeviceId, packet); return Task.FromResult(new Empty()); } @@ -74,7 +74,7 @@ public class PusherServiceGrpc( ErrorMessage = request.Packet.ErrorMessage }; foreach (var deviceId in request.DeviceIds) - webSocketService.SendPacketToDevice(deviceId, packet); + websocket.SendPacketToDevice(deviceId, packet); return Task.FromResult(new Empty()); } @@ -159,4 +159,22 @@ public class PusherServiceGrpc( await pushService.SendNotificationBatch(notification, accounts, request.Notification.IsSavable); return new Empty(); } + + public override async Task UnsubscribePushNotifications(UnsubscribePushNotificationsRequest request, ServerCallContext context) + { + await pushService.UnsubscribeDevice(request.DeviceId); + return new Empty(); + } + + public override Task GetWebsocketConnectionStatus(GetWebsocketConnectionStatusRequest request, ServerCallContext context) + { + var isConnected = request.IdCase switch + { + GetWebsocketConnectionStatusRequest.IdOneofCase.DeviceId => websocket.GetDeviceIsConnected(request.DeviceId), + GetWebsocketConnectionStatusRequest.IdOneofCase.UserId => websocket.GetAccountIsConnected(request.UserId), + _ => false + }; + + return Task.FromResult(new GetWebsocketConnectionStatusResponse { IsConnected = isConnected }); + } } \ No newline at end of file diff --git a/DysonNetwork.Shared/Auth/AuthScheme.cs b/DysonNetwork.Shared/Auth/AuthScheme.cs index 69d4873..7ddf4b0 100644 --- a/DysonNetwork.Shared/Auth/AuthScheme.cs +++ b/DysonNetwork.Shared/Auth/AuthScheme.cs @@ -16,10 +16,9 @@ public class DysonTokenAuthHandler( IOptionsMonitor options, ILoggerFactory logger, UrlEncoder encoder, - ISystemClock clock, AuthService.AuthServiceClient auth ) - : AuthenticationHandler(options, logger, encoder, clock) + : AuthenticationHandler(options, logger, encoder) { protected override async Task HandleAuthenticateAsync() { diff --git a/DysonNetwork.Shared/Auth/PermissionMiddleware.cs b/DysonNetwork.Shared/Auth/PermissionMiddleware.cs new file mode 100644 index 0000000..30711be --- /dev/null +++ b/DysonNetwork.Shared/Auth/PermissionMiddleware.cs @@ -0,0 +1,72 @@ +using DysonNetwork.Shared.Proto; +using Grpc.Core; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; + +namespace DysonNetwork.Shared.Auth +{ + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + public class RequiredPermissionAttribute(string area, string key) : Attribute + { + public string Area { get; set; } = area; + public string Key { get; } = key; + } + + public class PermissionMiddleware(RequestDelegate next) + { + public async Task InvokeAsync(HttpContext httpContext, PermissionService.PermissionServiceClient permissionService, ILogger logger) + { + var endpoint = httpContext.GetEndpoint(); + + var attr = endpoint?.Metadata + .OfType() + .FirstOrDefault(); + + if (attr != null) + { + if (httpContext.Items["CurrentUser"] is not Account currentUser) + { + httpContext.Response.StatusCode = StatusCodes.Status403Forbidden; + await httpContext.Response.WriteAsync("Unauthorized"); + return; + } + + // Assuming Account proto has a bool field 'is_superuser' which is generated as 'IsSuperuser' + if (currentUser.IsSuperuser) + { + // Bypass the permission check for performance + await next(httpContext); + return; + } + + var actor = $"user:{currentUser.Id}"; + + try + { + var permResp = await permissionService.HasPermissionAsync(new HasPermissionRequest + { + Actor = actor, + Area = attr.Area, + Key = attr.Key + }); + + if (!permResp.HasPermission) + { + httpContext.Response.StatusCode = StatusCodes.Status403Forbidden; + await httpContext.Response.WriteAsync($"Permission {attr.Area}/{attr.Key} was required."); + return; + } + } + catch (RpcException ex) + { + logger.LogError(ex, "gRPC call to PermissionService failed while checking permission {Area}/{Key} for actor {Actor}", attr.Area, attr.Key, actor); + httpContext.Response.StatusCode = StatusCodes.Status500InternalServerError; + await httpContext.Response.WriteAsync("Error checking permissions."); + return; + } + } + + await next(httpContext); + } + } +} diff --git a/DysonNetwork.Shared/Auth/Startup.cs b/DysonNetwork.Shared/Auth/Startup.cs index 6f29d03..7da4185 100644 --- a/DysonNetwork.Shared/Auth/Startup.cs +++ b/DysonNetwork.Shared/Auth/Startup.cs @@ -16,9 +16,9 @@ public static class DysonAuthStartup { var etcdClient = sp.GetRequiredService(); var config = sp.GetRequiredService(); - var clientCertPath = config["ClientCert:Path"]; - var clientKeyPath = config["ClientKey:Path"]; - var clientCertPassword = config["ClientCert:Password"]; + var clientCertPath = config["Service:ClientCert"]; + var clientKeyPath = config["Service:ClientKey"]; + var clientCertPassword = config["Service:CertPassword"]; return GrpcClientHelper .CreateAuthServiceClient(etcdClient, clientCertPath, clientKeyPath, clientCertPassword) diff --git a/DysonNetwork.Shared/DysonNetwork.Shared.csproj b/DysonNetwork.Shared/DysonNetwork.Shared.csproj index 38296a3..92e78ee 100644 --- a/DysonNetwork.Shared/DysonNetwork.Shared.csproj +++ b/DysonNetwork.Shared/DysonNetwork.Shared.csproj @@ -19,8 +19,7 @@ - - + diff --git a/DysonNetwork.Shared/Proto/GrpcClientHelper.cs b/DysonNetwork.Shared/Proto/GrpcClientHelper.cs index f891317..df42e78 100644 --- a/DysonNetwork.Shared/Proto/GrpcClientHelper.cs +++ b/DysonNetwork.Shared/Proto/GrpcClientHelper.cs @@ -33,17 +33,6 @@ public static class GrpcClientHelper return response.Kvs[0].Value.ToStringUtf8(); } - public static AccountService.AccountServiceClient CreateAccountServiceClient( - string url, - string clientCertPath, - string clientKeyPath, - string? clientCertPassword = null - ) - { - return new AccountService.AccountServiceClient(CreateCallInvoker(url, clientCertPath, clientKeyPath, - clientCertPassword)); - } - public static async Task CreateAccountServiceClient( IEtcdClient etcdClient, string clientCertPath, @@ -51,22 +40,11 @@ public static class GrpcClientHelper string? clientCertPassword = null ) { - var url = await GetServiceUrlFromEtcd(etcdClient, "AccountService"); + var url = await GetServiceUrlFromEtcd(etcdClient, "DysonNetwork.Pass"); return new AccountService.AccountServiceClient(CreateCallInvoker(url, clientCertPath, clientKeyPath, clientCertPassword)); } - public static AuthService.AuthServiceClient CreateAuthServiceClient( - string url, - string clientCertPath, - string clientKeyPath, - string? clientCertPassword = null - ) - { - return new AuthService.AuthServiceClient(CreateCallInvoker(url, clientCertPath, clientKeyPath, - clientCertPassword)); - } - public static async Task CreateAuthServiceClient( IEtcdClient etcdClient, string clientCertPath, @@ -74,22 +52,11 @@ public static class GrpcClientHelper string? clientCertPassword = null ) { - var url = await GetServiceUrlFromEtcd(etcdClient, "AuthService"); + var url = await GetServiceUrlFromEtcd(etcdClient, "DysonNetwork.Pass"); return new AuthService.AuthServiceClient(CreateCallInvoker(url, clientCertPath, clientKeyPath, clientCertPassword)); } - public static PusherService.PusherServiceClient CreatePusherServiceClient( - string url, - string clientCertPath, - string clientKeyPath, - string? clientCertPassword = null - ) - { - return new PusherService.PusherServiceClient(CreateCallInvoker(url, clientCertPath, clientKeyPath, - clientCertPassword)); - } - public static async Task CreatePusherServiceClient( IEtcdClient etcdClient, string clientCertPath, @@ -97,7 +64,7 @@ public static class GrpcClientHelper string? clientCertPassword = null ) { - var url = await GetServiceUrlFromEtcd(etcdClient, "PusherService"); + var url = await GetServiceUrlFromEtcd(etcdClient, "DysonNetwork.Pusher"); return new PusherService.PusherServiceClient(CreateCallInvoker(url, clientCertPath, clientKeyPath, clientCertPassword)); } diff --git a/DysonNetwork.Shared/Proto/account.proto b/DysonNetwork.Shared/Proto/account.proto index 142f52e..b7fad42 100644 --- a/DysonNetwork.Shared/Proto/account.proto +++ b/DysonNetwork.Shared/Proto/account.proto @@ -195,6 +195,7 @@ message LevelingInfo { service AccountService { // Account Operations rpc GetAccount(GetAccountRequest) returns (Account) {} + rpc GetAccountBatch(GetAccountBatchRequest) returns (GetAccountBatchResponse) {} rpc CreateAccount(CreateAccountRequest) returns (Account) {} rpc UpdateAccount(UpdateAccountRequest) returns (Account) {} rpc DeleteAccount(DeleteAccountRequest) returns (google.protobuf.Empty) {} @@ -243,6 +244,14 @@ message GetAccountRequest { string id = 1; // Account ID to retrieve } +message GetAccountBatchRequest { + repeated string id = 1; // Account ID to retrieve +} + +message GetAccountBatchResponse { + repeated Account accounts = 1; // List of accounts +} + message CreateAccountRequest { string name = 1; // Required: Unique username string nick = 2; // Optional: Display name diff --git a/DysonNetwork.Shared/Proto/pusher.proto b/DysonNetwork.Shared/Proto/pusher.proto index cf5d815..65d71a3 100644 --- a/DysonNetwork.Shared/Proto/pusher.proto +++ b/DysonNetwork.Shared/Proto/pusher.proto @@ -36,6 +36,12 @@ service PusherService { // Sends a push notification to a list of users. rpc SendPushNotificationToUsers(SendPushNotificationToUsersRequest) returns (google.protobuf.Empty) {} + + // Unsubscribes a device from push notifications. + rpc UnsubscribePushNotifications(UnsubscribePushNotificationsRequest) returns (google.protobuf.Empty) {} + + // Gets the WebSocket connection status for a device or user. + rpc GetWebsocketConnectionStatus(GetWebsocketConnectionStatusRequest) returns (GetWebsocketConnectionStatusResponse) {} } // Represents an email message. @@ -108,3 +114,18 @@ message SendPushNotificationToUsersRequest { repeated string user_ids = 1; PushNotification notification = 2; } + +message UnsubscribePushNotificationsRequest { + string device_id = 1; +} + +message GetWebsocketConnectionStatusRequest { + oneof id { + string device_id = 1; + string user_id = 2; + } +} + +message GetWebsocketConnectionStatusResponse { + bool is_connected = 1; +} diff --git a/DysonNetwork.Shared/Registry/ServiceHelper.cs b/DysonNetwork.Shared/Registry/ServiceHelper.cs new file mode 100644 index 0000000..47353ee --- /dev/null +++ b/DysonNetwork.Shared/Registry/ServiceHelper.cs @@ -0,0 +1,47 @@ +using dotnet_etcd.interfaces; +using DysonNetwork.Shared.Proto; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace DysonNetwork.Shared.Registry; + +public static class ServiceHelper +{ + public static IServiceCollection AddPusherService(this IServiceCollection services) + { + services.AddSingleton(sp => + { + var etcdClient = sp.GetRequiredService(); + var config = sp.GetRequiredService(); + var clientCertPath = config["Service:ClientCert"]; + var clientKeyPath = config["Service:ClientKey"]; + var clientCertPassword = config["Service:CertPassword"]; + + return GrpcClientHelper + .CreatePusherServiceClient(etcdClient, clientCertPath, clientKeyPath, clientCertPassword) + .GetAwaiter() + .GetResult(); + }); + + return services; + } + + public static IServiceCollection AddAccountService(this IServiceCollection services) + { + services.AddSingleton(sp => + { + var etcdClient = sp.GetRequiredService(); + var config = sp.GetRequiredService(); + var clientCertPath = config["Service:ClientCert"]; + var clientKeyPath = config["Service:ClientKey"]; + var clientCertPassword = config["Service:CertPassword"]; + + return GrpcClientHelper + .CreateAccountServiceClient(etcdClient, clientCertPath, clientKeyPath, clientCertPassword) + .GetAwaiter() + .GetResult(); + }); + + return services; + } +} \ No newline at end of file diff --git a/DysonNetwork.Shared/Registry/ServiceRegistry.cs b/DysonNetwork.Shared/Registry/ServiceRegistry.cs index 2266f8b..535326a 100644 --- a/DysonNetwork.Shared/Registry/ServiceRegistry.cs +++ b/DysonNetwork.Shared/Registry/ServiceRegistry.cs @@ -8,16 +8,20 @@ namespace DysonNetwork.Shared.Registry; public class ServiceRegistry(IEtcdClient etcd, ILogger logger) { - public async Task RegisterService(string serviceName, string serviceUrl, long leaseTtlSeconds = 60, CancellationToken cancellationToken = default) + public async Task RegisterService(string serviceName, string serviceUrl, long leaseTtlSeconds = 60, + CancellationToken cancellationToken = default) { var key = $"/services/{serviceName}"; - var leaseResponse = await etcd.LeaseGrantAsync(new LeaseGrantRequest { TTL = leaseTtlSeconds }); + var leaseResponse = await etcd.LeaseGrantAsync( + new LeaseGrantRequest { TTL = leaseTtlSeconds }, + cancellationToken: cancellationToken + ); await etcd.PutAsync(new PutRequest { Key = ByteString.CopyFrom(key, Encoding.UTF8), Value = ByteString.CopyFrom(serviceUrl, Encoding.UTF8), Lease = leaseResponse.ID - }); + }, cancellationToken: cancellationToken); _ = Task.Run(async () => { diff --git a/DysonNetwork.Sphere/Account/NotificationService.cs b/DysonNetwork.Sphere/Account/NotificationService.cs index fdb7502..00ebe80 100644 --- a/DysonNetwork.Sphere/Account/NotificationService.cs +++ b/DysonNetwork.Sphere/Account/NotificationService.cs @@ -190,7 +190,6 @@ public class NotificationService( Content = notification.Content, Meta = notification.Meta, Priority = notification.Priority, - Account = x, AccountId = x.Id }; return newNotification; diff --git a/DysonNetwork.Sphere/DysonNetwork.Sphere.csproj b/DysonNetwork.Sphere/DysonNetwork.Sphere.csproj index b65e109..ca9d807 100644 --- a/DysonNetwork.Sphere/DysonNetwork.Sphere.csproj +++ b/DysonNetwork.Sphere/DysonNetwork.Sphere.csproj @@ -30,7 +30,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/DysonNetwork.sln.DotSettings.user b/DysonNetwork.sln.DotSettings.user index caeddec..28064a5 100644 --- a/DysonNetwork.sln.DotSettings.user +++ b/DysonNetwork.sln.DotSettings.user @@ -3,6 +3,7 @@ ForceIncluded ForceIncluded ForceIncluded + ForceIncluded ForceIncluded ForceIncluded ForceIncluded @@ -28,6 +29,7 @@ ForceIncluded ForceIncluded ForceIncluded + ForceIncluded ForceIncluded ForceIncluded ForceIncluded @@ -81,6 +83,7 @@ ForceIncluded ForceIncluded ForceIncluded + ForceIncluded ForceIncluded ForceIncluded ForceIncluded From 007da589bff0cc9bb28205bfc6484e83ce21d326 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Mon, 14 Jul 2025 00:09:32 +0800 Subject: [PATCH 11/42] :recycle: Still don't know what I am doing. But basically the microservices are done. --- DysonNetwork.Drive/Program.cs | 2 +- .../Startup/ServiceCollectionExtensions.cs | 13 +++++++------ DysonNetwork.Drive/appsettings.json | 4 ++-- DysonNetwork.Pass/Program.cs | 1 + .../Startup/KestrelConfiguration.cs | 17 ----------------- .../Startup/ServiceCollectionExtensions.cs | 4 ++-- DysonNetwork.Pass/appsettings.json | 2 +- .../Startup/ServiceCollectionExtensions.cs | 4 ++-- DysonNetwork.Pusher/appsettings.json | 2 +- .../Http}/KestrelConfiguration.cs | 13 +++++++++++-- DysonNetwork.Shared/Proto/GrpcClientHelper.cs | 13 +++++++++---- 11 files changed, 37 insertions(+), 38 deletions(-) delete mode 100644 DysonNetwork.Pass/Startup/KestrelConfiguration.cs rename {DysonNetwork.Drive/Startup => DysonNetwork.Shared/Http}/KestrelConfiguration.cs (60%) diff --git a/DysonNetwork.Drive/Program.cs b/DysonNetwork.Drive/Program.cs index e701c37..480ed64 100644 --- a/DysonNetwork.Drive/Program.cs +++ b/DysonNetwork.Drive/Program.cs @@ -1,7 +1,7 @@ using DysonNetwork.Drive; using DysonNetwork.Drive.Startup; -using DysonNetwork.Pusher.Startup; using DysonNetwork.Shared.Auth; +using DysonNetwork.Shared.Http; using DysonNetwork.Shared.Registry; using Microsoft.EntityFrameworkCore; diff --git a/DysonNetwork.Drive/Startup/ServiceCollectionExtensions.cs b/DysonNetwork.Drive/Startup/ServiceCollectionExtensions.cs index a9b4ae1..fba75d1 100644 --- a/DysonNetwork.Drive/Startup/ServiceCollectionExtensions.cs +++ b/DysonNetwork.Drive/Startup/ServiceCollectionExtensions.cs @@ -34,7 +34,7 @@ public static class ServiceCollectionExtensions options.MaxReceiveMessageSize = 16 * 1024 * 1024; // 16MB options.MaxSendMessageSize = 16 * 1024 * 1024; // 16MB }); - + // Register gRPC reflection for service discovery services.AddGrpc(); @@ -69,7 +69,7 @@ public static class ServiceCollectionExtensions return services; } - + public static IServiceCollection AddAppFlushHandlers(this IServiceCollection services) { services.AddSingleton(); @@ -85,9 +85,10 @@ public static class ServiceCollectionExtensions options.SwaggerDoc("v1", new OpenApiInfo { Version = "v1", - Title = "DysonNetwork.Drive API", - Description = "DysonNetwork Drive Service", - TermsOfService = new Uri("https://example.com/terms"), // Update with actual terms + Title = "Dyson Drive", + Description = + "The file service of the Dyson Network. Mainly handling file storage and sharing. Also provide image processing and media analysis. Powered the Solar Network Drive as well.", + TermsOfService = new Uri("https://solsynth.dev/terms"), // Update with actual terms License = new OpenApiLicense { Name = "APGLv3", // Update with actual license @@ -127,4 +128,4 @@ public static class ServiceCollectionExtensions // Add your business services here return services; } -} +} \ No newline at end of file diff --git a/DysonNetwork.Drive/appsettings.json b/DysonNetwork.Drive/appsettings.json index 721bba8..df61dbb 100644 --- a/DysonNetwork.Drive/appsettings.json +++ b/DysonNetwork.Drive/appsettings.json @@ -9,7 +9,7 @@ }, "AllowedHosts": "*", "ConnectionStrings": { - "App": "Host=localhost;Port=5432;Database=dyson_network;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60", + "App": "Host=localhost;Port=5432;Database=dyson_drive;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60", "FastRetrieve": "localhost:6379", "Etcd": "etcd.orb.local:2379" }, @@ -129,7 +129,7 @@ ], "Service": { "Name": "DysonNetwork.Drive", - "Url": "http://localhost:5216", + "Url": "https://localhost:7092", "ClientCert": "../Certificates/client.crt", "ClientKey": "../Certificates/client.key" } diff --git a/DysonNetwork.Pass/Program.cs b/DysonNetwork.Pass/Program.cs index 2d11e2e..1dfa7d2 100644 --- a/DysonNetwork.Pass/Program.cs +++ b/DysonNetwork.Pass/Program.cs @@ -1,6 +1,7 @@ using DysonNetwork.Pass; using DysonNetwork.Pass.Account; using DysonNetwork.Pass.Startup; +using DysonNetwork.Shared.Http; using DysonNetwork.Shared.Registry; using Microsoft.EntityFrameworkCore; diff --git a/DysonNetwork.Pass/Startup/KestrelConfiguration.cs b/DysonNetwork.Pass/Startup/KestrelConfiguration.cs deleted file mode 100644 index b042534..0000000 --- a/DysonNetwork.Pass/Startup/KestrelConfiguration.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace DysonNetwork.Pass.Startup; - -public static class KestrelConfiguration -{ - public static WebApplicationBuilder ConfigureAppKestrel(this WebApplicationBuilder builder) - { - builder.Host.UseContentRoot(Directory.GetCurrentDirectory()); - builder.WebHost.ConfigureKestrel(options => - { - options.Limits.MaxRequestBodySize = 50 * 1024 * 1024; - options.Limits.KeepAliveTimeout = TimeSpan.FromMinutes(2); - options.Limits.RequestHeadersTimeout = TimeSpan.FromSeconds(30); - }); - - return builder; - } -} diff --git a/DysonNetwork.Pass/Startup/ServiceCollectionExtensions.cs b/DysonNetwork.Pass/Startup/ServiceCollectionExtensions.cs index 3f539e9..20c3279 100644 --- a/DysonNetwork.Pass/Startup/ServiceCollectionExtensions.cs +++ b/DysonNetwork.Pass/Startup/ServiceCollectionExtensions.cs @@ -132,8 +132,8 @@ public static class ServiceCollectionExtensions options.SwaggerDoc("v1", new OpenApiInfo { Version = "v1", - Title = "Solar Network API", - Description = "An open-source social network", + Title = "Dyson Pass", + Description = "The authentication service of the Dyson Network. Mainly handling authentication and authorization.", TermsOfService = new Uri("https://solsynth.dev/terms"), License = new OpenApiLicense { diff --git a/DysonNetwork.Pass/appsettings.json b/DysonNetwork.Pass/appsettings.json index 5a0f1e1..8dbd4e6 100644 --- a/DysonNetwork.Pass/appsettings.json +++ b/DysonNetwork.Pass/appsettings.json @@ -80,7 +80,7 @@ ], "Service": { "Name": "DysonNetwork.Pass", - "Url": "http://localhost:5216", + "Url": "https://localhost:7058", "ClientCert": "../Certificates/client.crt", "ClientKey": "../Certificates/client.key" }, diff --git a/DysonNetwork.Pusher/Startup/ServiceCollectionExtensions.cs b/DysonNetwork.Pusher/Startup/ServiceCollectionExtensions.cs index 2148160..1064dbf 100644 --- a/DysonNetwork.Pusher/Startup/ServiceCollectionExtensions.cs +++ b/DysonNetwork.Pusher/Startup/ServiceCollectionExtensions.cs @@ -84,8 +84,8 @@ public static class ServiceCollectionExtensions options.SwaggerDoc("v1", new OpenApiInfo { Version = "v1", - Title = "Solar Network API", - Description = "An open-source social network", + Title = "Dyson Pusher", + Description = "The pusher service of the Dyson Network. Mainly handling emailing, notifications and websockets.", TermsOfService = new Uri("https://solsynth.dev/terms"), License = new OpenApiLicense { diff --git a/DysonNetwork.Pusher/appsettings.json b/DysonNetwork.Pusher/appsettings.json index 2bea1db..59ec98b 100644 --- a/DysonNetwork.Pusher/appsettings.json +++ b/DysonNetwork.Pusher/appsettings.json @@ -36,7 +36,7 @@ ], "Service": { "Name": "DysonNetwork.Pusher", - "Url": "http://localhost:5212", + "Url": "https://localhost:7259", "ClientCert": "../Certificates/client.crt", "ClientKey": "../Certificates/client.key" }, diff --git a/DysonNetwork.Drive/Startup/KestrelConfiguration.cs b/DysonNetwork.Shared/Http/KestrelConfiguration.cs similarity index 60% rename from DysonNetwork.Drive/Startup/KestrelConfiguration.cs rename to DysonNetwork.Shared/Http/KestrelConfiguration.cs index f35e4dd..a023a75 100644 --- a/DysonNetwork.Drive/Startup/KestrelConfiguration.cs +++ b/DysonNetwork.Shared/Http/KestrelConfiguration.cs @@ -1,4 +1,9 @@ -namespace DysonNetwork.Pusher.Startup; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Server.Kestrel.Core; +using Microsoft.Extensions.Hosting; + +namespace DysonNetwork.Shared.Http; public static class KestrelConfiguration { @@ -10,8 +15,12 @@ public static class KestrelConfiguration options.Limits.MaxRequestBodySize = 50 * 1024 * 1024; options.Limits.KeepAliveTimeout = TimeSpan.FromMinutes(2); options.Limits.RequestHeadersTimeout = TimeSpan.FromSeconds(30); + options.ConfigureEndpointDefaults(endpoints => + { + endpoints.Protocols = HttpProtocols.Http1AndHttp2; + }); }); return builder; } -} +} \ No newline at end of file diff --git a/DysonNetwork.Shared/Proto/GrpcClientHelper.cs b/DysonNetwork.Shared/Proto/GrpcClientHelper.cs index df42e78..49ce2c6 100644 --- a/DysonNetwork.Shared/Proto/GrpcClientHelper.cs +++ b/DysonNetwork.Shared/Proto/GrpcClientHelper.cs @@ -1,3 +1,4 @@ +using System.Net; using Grpc.Net.Client; using System.Security.Cryptography.X509Certificates; using Grpc.Core; @@ -16,11 +17,14 @@ public static class GrpcClientHelper { var handler = new HttpClientHandler(); handler.ClientCertificates.Add( - clientCertPassword is null ? - X509Certificate2.CreateFromPemFile(clientCertPath, clientKeyPath) : - X509Certificate2.CreateFromEncryptedPemFile(clientCertPath, clientCertPassword, clientKeyPath) + clientCertPassword is null + ? X509Certificate2.CreateFromPemFile(clientCertPath, clientKeyPath) + : X509Certificate2.CreateFromEncryptedPemFile(clientCertPath, clientCertPassword, clientKeyPath) ); - return GrpcChannel.ForAddress(url, new GrpcChannelOptions { HttpHandler = handler }).CreateCallInvoker(); + var httpClient = new HttpClient(handler); + httpClient.DefaultRequestVersion = HttpVersion.Version20; + httpClient.DefaultVersionPolicy = HttpVersionPolicy.RequestVersionOrHigher; + return GrpcChannel.ForAddress(url, new GrpcChannelOptions { HttpClient = httpClient }).CreateCallInvoker(); } private static async Task GetServiceUrlFromEtcd(IEtcdClient etcdClient, string serviceName) @@ -30,6 +34,7 @@ public static class GrpcClientHelper { throw new InvalidOperationException($"Service '{serviceName}' not found in Etcd."); } + return response.Kvs[0].Value.ToStringUtf8(); } From 387246a95cf3a15c2e523bc57b82d7b23ad5732f Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Mon, 14 Jul 2025 02:13:51 +0800 Subject: [PATCH 12/42] :sparkles: Protobuf update --- DysonNetwork.Drive/DysonNetwork.Drive.csproj | 4 + DysonNetwork.Drive/Storage/CloudFile.cs | 60 +++- .../Data/CloudFileReferenceObject.cs | 5 +- DysonNetwork.Shared/Proto/account.proto | 4 +- DysonNetwork.Shared/Proto/file.proto | 256 ++++++++++++++++-- DysonNetwork.sln.DotSettings.user | 1 + 6 files changed, 302 insertions(+), 28 deletions(-) diff --git a/DysonNetwork.Drive/DysonNetwork.Drive.csproj b/DysonNetwork.Drive/DysonNetwork.Drive.csproj index 9edd5ba..19bfcab 100644 --- a/DysonNetwork.Drive/DysonNetwork.Drive.csproj +++ b/DysonNetwork.Drive/DysonNetwork.Drive.csproj @@ -67,4 +67,8 @@ + + + + diff --git a/DysonNetwork.Drive/Storage/CloudFile.cs b/DysonNetwork.Drive/Storage/CloudFile.cs index c5c6f35..6911dc2 100644 --- a/DysonNetwork.Drive/Storage/CloudFile.cs +++ b/DysonNetwork.Drive/Storage/CloudFile.cs @@ -1,9 +1,8 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; -using System.Text.Json.Serialization; using DysonNetwork.Shared.Data; -using DysonNetwork.Shared.Proto; using NodaTime; +using NodaTime.Serialization.Protobuf; namespace DysonNetwork.Drive.Storage; @@ -97,6 +96,46 @@ public class CloudFile : ModelBase, ICloudFile, IIdentifiedResource } public string ResourceIdentifier => $"file/{Id}"; + + /// + /// Converts the CloudFile to a protobuf message + /// + /// The protobuf message representation of this object + public Shared.Proto.CloudFile ToProtoValue() + { + var protoFile = new Shared.Proto.CloudFile + { + Id = Id, + Name = Name ?? string.Empty, + MimeType = MimeType ?? string.Empty, + Hash = Hash ?? string.Empty, + Size = Size, + HasCompression = HasCompression, + Url = StorageUrl ?? string.Empty, + ContentType = MimeType ?? string.Empty, + UploadedAt = UploadedAt?.ToTimestamp() + }; + + // Convert FileMeta dictionary + if (FileMeta != null) + { + foreach (var (key, value) in FileMeta) + { + protoFile.FileMeta[key] = Google.Protobuf.WellKnownTypes.Value.ForString(value?.ToString() ?? string.Empty); + } + } + + // Convert UserMeta dictionary + if (UserMeta != null) + { + foreach (var (key, value) in UserMeta) + { + protoFile.UserMeta[key] = Google.Protobuf.WellKnownTypes.Value.ForString(value?.ToString() ?? string.Empty); + } + } + + return protoFile; + } } public enum ContentSensitiveMark @@ -128,4 +167,21 @@ public class CloudFileReference : ModelBase /// Optional expiration date for the file reference ///
public Instant? ExpiredAt { get; set; } + + /// + /// Converts the CloudFileReference to a protobuf message + /// + /// The protobuf message representation of this object + public Shared.Proto.CloudFileReference ToProtoValue() + { + return new Shared.Proto.CloudFileReference + { + Id = Id.ToString(), + FileId = FileId, + File = File?.ToProtoValue(), + Usage = Usage, + ResourceId = ResourceId, + ExpiredAt = ExpiredAt?.ToTimestamp() + }; + } } \ No newline at end of file diff --git a/DysonNetwork.Shared/Data/CloudFileReferenceObject.cs b/DysonNetwork.Shared/Data/CloudFileReferenceObject.cs index 37c0eab..b20826f 100644 --- a/DysonNetwork.Shared/Data/CloudFileReferenceObject.cs +++ b/DysonNetwork.Shared/Data/CloudFileReferenceObject.cs @@ -20,9 +20,9 @@ public class CloudFileReferenceObject : ModelBase, ICloudFile /// /// Converts the current object to its protobuf representation /// - public global::DysonNetwork.Shared.Proto.CloudFileReferenceObject ToProtoValue() + public Proto.CloudFile ToProtoValue() { - var proto = new global::DysonNetwork.Shared.Proto.CloudFileReferenceObject + var proto = new Proto.CloudFile { Id = Id, Name = Name, @@ -30,7 +30,6 @@ public class CloudFileReferenceObject : ModelBase, ICloudFile Hash = Hash ?? string.Empty, Size = Size, HasCompression = HasCompression, - // Backward compatibility fields ContentType = MimeType ?? string.Empty, Url = string.Empty // This should be set by the caller if needed }; diff --git a/DysonNetwork.Shared/Proto/account.proto b/DysonNetwork.Shared/Proto/account.proto index b7fad42..59afef5 100644 --- a/DysonNetwork.Shared/Proto/account.proto +++ b/DysonNetwork.Shared/Proto/account.proto @@ -55,8 +55,8 @@ message AccountProfile { google.protobuf.StringValue picture_id = 17; google.protobuf.StringValue background_id = 18; - CloudFileReferenceObject picture = 19; - CloudFileReferenceObject background = 20; + CloudFile picture = 19; + CloudFile background = 20; string account_id = 21; } diff --git a/DysonNetwork.Shared/Proto/file.proto b/DysonNetwork.Shared/Proto/file.proto index 593e2b7..92cd6f7 100644 --- a/DysonNetwork.Shared/Proto/file.proto +++ b/DysonNetwork.Shared/Proto/file.proto @@ -8,10 +8,11 @@ import "google/protobuf/timestamp.proto"; import "google/protobuf/struct.proto"; import "google/protobuf/empty.proto"; import "google/protobuf/field_mask.proto"; +import "google/protobuf/duration.proto"; -// CloudFileReferenceObject represents a reference to a file stored in cloud storage. +// CloudFile represents a reference to a file stored in cloud storage. // It contains metadata about the file that won't change, helping to reduce database load. -message CloudFileReferenceObject { +message CloudFile { // Unique identifier for the file string id = 1; @@ -36,32 +37,44 @@ message CloudFileReferenceObject { // Indicates if the file is stored with compression bool has_compression = 8; - // URL to access the file (kept for backward compatibility) + // URL to access the file string url = 9; - // Content type of the file (kept for backward compatibility) + // Content type of the file string content_type = 10; - // When the file was uploaded (kept for backward compatibility) + // When the file was uploaded google.protobuf.Timestamp uploaded_at = 11; - - // Additional metadata (kept for backward compatibility) - map metadata = 12; } // Service for file operations service FileService { // Get file reference by ID - rpc GetFile(GetFileRequest) returns (CloudFileReferenceObject) {} - - // Create a new file reference - rpc CreateFile(CreateFileRequest) returns (CloudFileReferenceObject) {} + rpc GetFile(GetFileRequest) returns (CloudFile); // Update an existing file reference - rpc UpdateFile(UpdateFileRequest) returns (CloudFileReferenceObject) {} + rpc UpdateFile(UpdateFileRequest) returns (CloudFile); // Delete a file reference - rpc DeleteFile(DeleteFileRequest) returns (google.protobuf.Empty) {} + rpc DeleteFile(DeleteFileRequest) returns (google.protobuf.Empty); + + // Process and upload a new file + rpc ProcessNewFile(stream ProcessNewFileRequest) returns (CloudFile); + + // Upload a file to remote storage + rpc UploadFileToRemote(stream UploadFileToRemoteRequest) returns (CloudFile); + + // Delete file data from storage + rpc DeleteFileData(DeleteFileDataRequest) returns (google.protobuf.Empty); + + // Load files from references + rpc LoadFromReference(LoadFromReferenceRequest) returns (LoadFromReferenceResponse); + + // Check if a file is referenced by any resource + rpc IsReferenced(IsReferencedRequest) returns (IsReferencedResponse); + + // Purge cache for a file + rpc PurgeCache(PurgeCacheRequest) returns (google.protobuf.Empty); } // Request message for GetFile @@ -69,19 +82,220 @@ message GetFileRequest { string id = 1; } -// Request message for CreateFile -message CreateFileRequest { - CloudFileReferenceObject file = 1; -} - // Request message for UpdateFile message UpdateFileRequest { - CloudFileReferenceObject file = 1; + CloudFile file = 1; google.protobuf.FieldMask update_mask = 2; } // Request message for DeleteFile +message ProcessNewFileRequest { + oneof data { + FileMetadata metadata = 1; + bytes chunk = 2; + } +} + +message FileMetadata { + string file_id = 1; + string file_name = 2; + string content_type = 3; + string account_id = 4; +} + +message UploadFileToRemoteRequest { + oneof data { + UploadMetadata metadata = 1; + bytes chunk = 2; + } +} + +message UploadMetadata { + string file_id = 1; + string target_remote = 2; + string suffix = 3; + string content_type = 4; +} + message DeleteFileRequest { string id = 1; - bool hard_delete = 2; + bool purge = 2; +} + +message DeleteFileDataRequest { + string file_id = 1; + bool force = 2; +} + +message LoadFromReferenceRequest { + repeated string reference_ids = 1; +} + +message LoadFromReferenceResponse { + repeated CloudFile files = 1; +} + +message GetReferenceCountRequest { + string file_id = 1; +} + +message GetReferenceCountResponse { + int32 count = 1; +} + +message IsReferencedRequest { + string file_id = 1; +} + +message IsReferencedResponse { + bool is_referenced = 1; +} + +message PurgeCacheRequest { + string file_id = 1; +} + +// CloudFileReference represents a reference to a CloudFile with additional metadata +// about its usage in the system. +message CloudFileReference { + // Unique identifier for the reference + string id = 1; + + // Reference to the actual file + string file_id = 2; + + // The actual file data (optional, can be populated when needed) + CloudFile file = 3; + + // Description of how this file is being used + string usage = 4; + + // ID of the resource that this file is associated with + string resource_id = 5; + + // Optional expiration timestamp for the reference + google.protobuf.Timestamp expired_at = 6; +} + +// Request/Response messages for FileReferenceService +message CreateReferenceRequest { + string file_id = 1; + string usage = 2; + string resource_id = 3; + google.protobuf.Timestamp expired_at = 4; + google.protobuf.Duration duration = 5; // Alternative to expired_at +} + +message GetReferencesRequest { + string file_id = 1; +} + +message GetReferencesResponse { + repeated CloudFileReference references = 1; +} + +message GetResourceReferencesRequest { + string resource_id = 1; + string usage = 2; // Optional +} + +message GetResourceFilesRequest { + string resource_id = 1; + string usage = 2; // Optional +} + +message GetResourceFilesResponse { + repeated CloudFile files = 1; +} + +message DeleteResourceReferencesRequest { + string resource_id = 1; + string usage = 2; // Optional +} + +message DeleteResourceReferencesResponse { + int32 deleted_count = 1; +} + +message DeleteReferenceRequest { + string reference_id = 1; +} + +message DeleteReferenceResponse { + bool success = 1; +} + +message UpdateResourceFilesRequest { + string resource_id = 1; + repeated string file_ids = 2; + string usage = 3; + google.protobuf.Timestamp expired_at = 4; + google.protobuf.Duration duration = 5; // Alternative to expired_at +} + +message UpdateResourceFilesResponse { + repeated CloudFileReference references = 1; +} + +message SetReferenceExpirationRequest { + string reference_id = 1; + google.protobuf.Timestamp expired_at = 2; + google.protobuf.Duration duration = 3; // Alternative to expired_at +} + +message SetReferenceExpirationResponse { + bool success = 1; +} + +message SetFileReferencesExpirationRequest { + string file_id = 1; + google.protobuf.Timestamp expired_at = 2; +} + +message SetFileReferencesExpirationResponse { + int32 updated_count = 1; +} + +message HasFileReferencesRequest { + string file_id = 1; +} + +message HasFileReferencesResponse { + bool has_references = 1; +} + +// Service for managing file references +service FileReferenceService { + // Creates a new reference to a file for a specific resource + rpc CreateReference(CreateReferenceRequest) returns (CloudFileReference); + + // Gets all references to a file + rpc GetReferences(GetReferencesRequest) returns (GetReferencesResponse); + + // Gets the number of references to a file + rpc GetReferenceCount(GetReferenceCountRequest) returns (GetReferenceCountResponse); + + // Gets all references for a specific resource and optional usage + rpc GetResourceReferences(GetResourceReferencesRequest) returns (GetReferencesResponse); + + // Gets all files referenced by a resource with optional usage filter + rpc GetResourceFiles(GetResourceFilesRequest) returns (GetResourceFilesResponse); + + // Deletes references for a specific resource and optional usage + rpc DeleteResourceReferences(DeleteResourceReferencesRequest) returns (DeleteResourceReferencesResponse); + + // Deletes a specific file reference + rpc DeleteReference(DeleteReferenceRequest) returns (DeleteReferenceResponse); + + // Updates the files referenced by a resource + rpc UpdateResourceFiles(UpdateResourceFilesRequest) returns (UpdateResourceFilesResponse); + + // Updates the expiration time for a file reference + rpc SetReferenceExpiration(SetReferenceExpirationRequest) returns (SetReferenceExpirationResponse); + + // Updates the expiration time for all references to a file + rpc SetFileReferencesExpiration(SetFileReferencesExpirationRequest) returns (SetFileReferencesExpirationResponse); + + // Checks if a file has any references + rpc HasFileReferences(HasFileReferencesRequest) returns (HasFileReferencesResponse); } diff --git a/DysonNetwork.sln.DotSettings.user b/DysonNetwork.sln.DotSettings.user index 28064a5..c388d6b 100644 --- a/DysonNetwork.sln.DotSettings.user +++ b/DysonNetwork.sln.DotSettings.user @@ -43,6 +43,7 @@ ForceIncluded ForceIncluded ForceIncluded + ForceIncluded ForceIncluded ForceIncluded ForceIncluded From 28067d18f67849f3d0510dbe75337f5885f37f29 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Mon, 14 Jul 2025 03:01:51 +0800 Subject: [PATCH 13/42] :bricks: File service basis --- DysonNetwork.Drive/DysonNetwork.Drive.csproj | 5 - .../Storage/FileReferenceServiceGrpc.cs | 127 +++++++++++++ DysonNetwork.Drive/Storage/FileServiceGrpc.cs | 175 ++++++++++++++++++ 3 files changed, 302 insertions(+), 5 deletions(-) create mode 100644 DysonNetwork.Drive/Storage/FileReferenceServiceGrpc.cs create mode 100644 DysonNetwork.Drive/Storage/FileServiceGrpc.cs diff --git a/DysonNetwork.Drive/DysonNetwork.Drive.csproj b/DysonNetwork.Drive/DysonNetwork.Drive.csproj index 19bfcab..0543986 100644 --- a/DysonNetwork.Drive/DysonNetwork.Drive.csproj +++ b/DysonNetwork.Drive/DysonNetwork.Drive.csproj @@ -66,9 +66,4 @@ - - - - -
diff --git a/DysonNetwork.Drive/Storage/FileReferenceServiceGrpc.cs b/DysonNetwork.Drive/Storage/FileReferenceServiceGrpc.cs new file mode 100644 index 0000000..758efe2 --- /dev/null +++ b/DysonNetwork.Drive/Storage/FileReferenceServiceGrpc.cs @@ -0,0 +1,127 @@ +using System.Threading.Tasks; +using DysonNetwork.Shared.Proto; +using Google.Protobuf.WellKnownTypes; +using Grpc.Core; +using NodaTime; +using Duration = NodaTime.Duration; + +namespace DysonNetwork.Drive.Storage +{ + public class FileReferenceServiceGrpc(FileReferenceService fileReferenceService) : Shared.Proto.FileReferenceService.FileReferenceServiceBase + { + public override async Task CreateReference(CreateReferenceRequest request, ServerCallContext context) + { + Instant? expiredAt = null; + if (request.ExpiredAt != null) + { + expiredAt = Instant.FromUnixTimeSeconds(request.ExpiredAt.Seconds); + } + else if (request.Duration != null) + { + expiredAt = SystemClock.Instance.GetCurrentInstant() + Duration.FromTimeSpan(request.Duration.ToTimeSpan()); + } + + var reference = await fileReferenceService.CreateReferenceAsync( + request.FileId, + request.Usage, + request.ResourceId, + expiredAt + ); + return reference.ToProtoValue(); + } + + public override async Task GetReferences(GetReferencesRequest request, ServerCallContext context) + { + var references = await fileReferenceService.GetReferencesAsync(request.FileId); + var response = new GetReferencesResponse(); + response.References.AddRange(references.Select(r => r.ToProtoValue())); + return response; + } + + public override async Task GetReferenceCount(GetReferenceCountRequest request, ServerCallContext context) + { + var count = await fileReferenceService.GetReferenceCountAsync(request.FileId); + return new GetReferenceCountResponse { Count = count }; + } + + public override async Task GetResourceReferences(GetResourceReferencesRequest request, ServerCallContext context) + { + var references = await fileReferenceService.GetResourceReferencesAsync(request.ResourceId, request.Usage); + var response = new GetReferencesResponse(); + response.References.AddRange(references.Select(r => r.ToProtoValue())); + return response; + } + + public override async Task GetResourceFiles(GetResourceFilesRequest request, ServerCallContext context) + { + var files = await fileReferenceService.GetResourceFilesAsync(request.ResourceId, request.Usage); + var response = new GetResourceFilesResponse(); + response.Files.AddRange(files.Select(f => f.ToProtoValue())); + return response; + } + + public override async Task DeleteResourceReferences(DeleteResourceReferencesRequest request, ServerCallContext context) + { + var deletedCount = await fileReferenceService.DeleteResourceReferencesAsync(request.ResourceId, request.Usage); + return new DeleteResourceReferencesResponse { DeletedCount = deletedCount }; + } + + public override async Task DeleteReference(DeleteReferenceRequest request, ServerCallContext context) + { + var success = await fileReferenceService.DeleteReferenceAsync(Guid.Parse(request.ReferenceId)); + return new DeleteReferenceResponse { Success = success }; + } + + public override async Task UpdateResourceFiles(UpdateResourceFilesRequest request, ServerCallContext context) + { + Instant? expiredAt = null; + if (request.ExpiredAt != null) + { + expiredAt = Instant.FromUnixTimeSeconds(request.ExpiredAt.Seconds); + } + else if (request.Duration != null) + { + expiredAt = SystemClock.Instance.GetCurrentInstant() + Duration.FromTimeSpan(request.Duration.ToTimeSpan()); + } + + var references = await fileReferenceService.UpdateResourceFilesAsync( + request.ResourceId, + request.FileIds, + request.Usage, + expiredAt + ); + var response = new UpdateResourceFilesResponse(); + response.References.AddRange(references.Select(r => r.ToProtoValue())); + return response; + } + + public override async Task SetReferenceExpiration(SetReferenceExpirationRequest request, ServerCallContext context) + { + Instant? expiredAt = null; + if (request.ExpiredAt != null) + { + expiredAt = Instant.FromUnixTimeSeconds(request.ExpiredAt.Seconds); + } + else if (request.Duration != null) + { + expiredAt = SystemClock.Instance.GetCurrentInstant() + Duration.FromTimeSpan(request.Duration.ToTimeSpan()); + } + + var success = await fileReferenceService.SetReferenceExpirationAsync(Guid.Parse(request.ReferenceId), expiredAt); + return new SetReferenceExpirationResponse { Success = success }; + } + + public override async Task SetFileReferencesExpiration(SetFileReferencesExpirationRequest request, ServerCallContext context) + { + var expiredAt = Instant.FromUnixTimeSeconds(request.ExpiredAt.Seconds); + var updatedCount = await fileReferenceService.SetFileReferencesExpirationAsync(request.FileId, expiredAt); + return new SetFileReferencesExpirationResponse { UpdatedCount = updatedCount }; + } + + public override async Task HasFileReferences(HasFileReferencesRequest request, ServerCallContext context) + { + var hasReferences = await fileReferenceService.HasFileReferencesAsync(request.FileId); + return new HasFileReferencesResponse { HasReferences = hasReferences }; + } + } +} \ No newline at end of file diff --git a/DysonNetwork.Drive/Storage/FileServiceGrpc.cs b/DysonNetwork.Drive/Storage/FileServiceGrpc.cs new file mode 100644 index 0000000..d3118a5 --- /dev/null +++ b/DysonNetwork.Drive/Storage/FileServiceGrpc.cs @@ -0,0 +1,175 @@ +using System.Threading.Tasks; +using DysonNetwork.Shared.Proto; +using Google.Protobuf.WellKnownTypes; +using Grpc.Core; + +namespace DysonNetwork.Drive.Storage +{ + public class FileServiceGrpc(FileService fileService) : Shared.Proto.FileService.FileServiceBase + { + public override async Task GetFile(GetFileRequest request, ServerCallContext context) + { + var file = await fileService.GetFileAsync(request.Id); + return file?.ToProtoValue() ?? throw new RpcException(new Status(StatusCode.NotFound, "File not found")); + } + + public override async Task UpdateFile(UpdateFileRequest request, ServerCallContext context) + { + // Assuming UpdateFileAsync exists in FileService and handles the update_mask + // This is a placeholder, as the current FileService.cs doesn't have a direct UpdateFile method + // You might need to implement this logic in FileService based on your needs. + // For now, we'll just return the requested file. + var file = await fileService.GetFileAsync(request.File.Id); + if (file == null) + { + throw new RpcException(new Status(StatusCode.NotFound, "File not found")); + } + + // Apply updates from request.File to 'file' based on request.UpdateMask + // This part requires more detailed implementation based on how you want to handle partial updates. + return file.ToProtoValue(); + } + + public override async Task DeleteFile(DeleteFileRequest request, ServerCallContext context) + { + var file = await fileService.GetFileAsync(request.Id); + if (file == null) + { + throw new RpcException(new Status(StatusCode.NotFound, "File not found")); + } + + await fileService.DeleteFileAsync(file); + return new Empty(); + } + + public override async Task ProcessNewFile(IAsyncStreamReader requestStream, + ServerCallContext context) + { + ProcessNewFileRequest? metadataRequest = null; + var chunks = new List(); + + await foreach (var message in requestStream.ReadAllAsync()) + { + if (message.DataCase == ProcessNewFileRequest.DataOneofCase.Metadata) + { + metadataRequest = message; + } + else if (message.DataCase == ProcessNewFileRequest.DataOneofCase.Chunk) + { + chunks.Add(message.Chunk.ToByteArray()); + } + } + + if (metadataRequest == null || metadataRequest.Metadata == null) + { + throw new RpcException(new Status(StatusCode.InvalidArgument, "Missing file metadata")); + } + + var metadata = metadataRequest.Metadata; + using var memoryStream = new MemoryStream(); + foreach (var chunk in chunks) + { + await memoryStream.WriteAsync(chunk); + } + + memoryStream.Position = 0; + + // Assuming you have an Account object available or can create a dummy one for now + // You might need to adjust this based on how accounts are handled in your system + var dummyAccount = new Account { Id = metadata.AccountId }; + + var cloudFile = await fileService.ProcessNewFileAsync( + dummyAccount, + metadata.FileId, + memoryStream, + metadata.FileName, + metadata.ContentType + ); + return cloudFile.ToProtoValue(); + } + + public override async Task UploadFileToRemote( + IAsyncStreamReader requestStream, ServerCallContext context) + { + UploadFileToRemoteRequest? metadataRequest = null; + var chunks = new List(); + + await foreach (var message in requestStream.ReadAllAsync()) + { + if (message.DataCase == UploadFileToRemoteRequest.DataOneofCase.Metadata) + { + metadataRequest = message; + } + else if (message.DataCase == UploadFileToRemoteRequest.DataOneofCase.Chunk) + { + chunks.Add(message.Chunk.ToByteArray()); + } + } + + if (metadataRequest == null || metadataRequest.Metadata == null) + { + throw new RpcException(new Status(StatusCode.InvalidArgument, "Missing upload metadata")); + } + + var metadata = metadataRequest.Metadata; + using var memoryStream = new MemoryStream(); + foreach (var chunk in chunks) + { + await memoryStream.WriteAsync(chunk); + } + + memoryStream.Position = 0; + + var file = await fileService.GetFileAsync(metadata.FileId); + if (file == null) + { + throw new RpcException(new Status(StatusCode.NotFound, "File not found")); + } + + var uploadedFile = await fileService.UploadFileToRemoteAsync( + file, + memoryStream, + metadata.TargetRemote, + metadata.Suffix + ); + return uploadedFile.ToProtoValue(); + } + + public override async Task DeleteFileData(DeleteFileDataRequest request, ServerCallContext context) + { + var file = await fileService.GetFileAsync(request.FileId); + if (file == null) + { + throw new RpcException(new Status(StatusCode.NotFound, "File not found")); + } + + await fileService.DeleteFileDataAsync(file); + return new Empty(); + } + + public override async Task LoadFromReference(LoadFromReferenceRequest request, + ServerCallContext context) + { + // Assuming CloudFileReferenceObject is a simple class/struct that holds an ID + // You might need to define this or adjust the LoadFromReference method in FileService + var references = request.ReferenceIds.Select(id => new CloudFileReferenceObject { Id = id }).ToList(); + var files = await fileService.LoadFromReference(references); + var response = new LoadFromReferenceResponse(); + response.Files.AddRange(files.Where(f => f != null).Select(f => f!.ToProtoValue())); + return response; + } + + public override async Task IsReferenced(IsReferencedRequest request, + ServerCallContext context) + { + var isReferenced = await fileService.IsReferencedAsync(request.FileId); + return new IsReferencedResponse { IsReferenced = isReferenced }; + } + + public override async Task PurgeCache(PurgeCacheRequest request, ServerCallContext context) + { + await fileService._PurgeCacheAsync(request.FileId); + return new Empty(); + } + } +} \ No newline at end of file From 92ab7a1a2af749e785a9cd0a511663bbddf65ffa Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Mon, 14 Jul 2025 03:07:03 +0800 Subject: [PATCH 14/42] :sparkles: Still don't know what I am doing --- DysonNetwork.Drive/Storage/FileService.cs | 63 +++++++++++++++++++ DysonNetwork.Drive/Storage/FileServiceGrpc.cs | 12 +--- 2 files changed, 65 insertions(+), 10 deletions(-) diff --git a/DysonNetwork.Drive/Storage/FileService.cs b/DysonNetwork.Drive/Storage/FileService.cs index 7387de0..d18868d 100644 --- a/DysonNetwork.Drive/Storage/FileService.cs +++ b/DysonNetwork.Drive/Storage/FileService.cs @@ -3,6 +3,7 @@ using FFMpegCore; using System.Security.Cryptography; using DysonNetwork.Shared.Cache; using DysonNetwork.Shared.Proto; +using Google.Protobuf.WellKnownTypes; using Microsoft.EntityFrameworkCore; using Minio; using Minio.DataModel.Args; @@ -344,6 +345,68 @@ public class FileService( return file; } + public async Task UpdateFileAsync(CloudFile file, FieldMask updateMask) + { + var existingFile = await db.Files.FirstOrDefaultAsync(f => f.Id == file.Id); + if (existingFile == null) + { + throw new InvalidOperationException($"File with ID {file.Id} not found."); + } + + foreach (var path in updateMask.Paths) + { + switch (path) + { + case "name": + existingFile.Name = file.Name; + break; + case "description": + existingFile.Description = file.Description; + break; + case "file_meta": + existingFile.FileMeta = file.FileMeta; + break; + case "user_meta": + existingFile.UserMeta = file.UserMeta; + break; + case "mime_type": + existingFile.MimeType = file.MimeType; + break; + case "hash": + existingFile.Hash = file.Hash; + break; + case "size": + existingFile.Size = file.Size; + break; + case "uploaded_at": + existingFile.UploadedAt = file.UploadedAt; + break; + case "uploaded_to": + existingFile.UploadedTo = file.UploadedTo; + break; + case "has_compression": + existingFile.HasCompression = file.HasCompression; + break; + case "is_marked_recycle": + existingFile.IsMarkedRecycle = file.IsMarkedRecycle; + break; + case "storage_id": + existingFile.StorageId = file.StorageId; + break; + case "storage_url": + existingFile.StorageUrl = file.StorageUrl; + break; + default: + logger.LogWarning("Attempted to update unknown field: {Field}", path); + break; + } + } + + await db.SaveChangesAsync(); + await _PurgeCacheAsync(file.Id); + return existingFile; + } + public async Task DeleteFileAsync(CloudFile file) { await DeleteFileDataAsync(file); diff --git a/DysonNetwork.Drive/Storage/FileServiceGrpc.cs b/DysonNetwork.Drive/Storage/FileServiceGrpc.cs index d3118a5..ea1e735 100644 --- a/DysonNetwork.Drive/Storage/FileServiceGrpc.cs +++ b/DysonNetwork.Drive/Storage/FileServiceGrpc.cs @@ -15,19 +15,11 @@ namespace DysonNetwork.Drive.Storage public override async Task UpdateFile(UpdateFileRequest request, ServerCallContext context) { - // Assuming UpdateFileAsync exists in FileService and handles the update_mask - // This is a placeholder, as the current FileService.cs doesn't have a direct UpdateFile method - // You might need to implement this logic in FileService based on your needs. - // For now, we'll just return the requested file. var file = await fileService.GetFileAsync(request.File.Id); if (file == null) - { throw new RpcException(new Status(StatusCode.NotFound, "File not found")); - } - - // Apply updates from request.File to 'file' based on request.UpdateMask - // This part requires more detailed implementation based on how you want to handle partial updates. - return file.ToProtoValue(); + var updatedFile = await fileService.UpdateFileAsync(file, request.UpdateMask); + return updatedFile.ToProtoValue(); } public override async Task DeleteFile(DeleteFileRequest request, ServerCallContext context) From 06f1cc3ca19af649c428e7dd0489e5a3a853151c Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Mon, 14 Jul 2025 11:12:37 +0800 Subject: [PATCH 15/42] :recycle: Remix things up --- DysonNetwork.Drive/Program.cs | 7 +- .../Startup/ApplicationBuilderExtensions.cs | 11 ++- .../Startup/ServiceCollectionExtensions.cs | 17 ++++- .../Storage/FileReferenceServiceGrpc.cs | 6 -- DysonNetwork.Drive/Storage/FileServiceGrpc.cs | 1 - .../Account/AccountEventService.cs | 22 ++++-- DysonNetwork.Pass/Program.cs | 1 + DysonNetwork.Shared/Proto/file.proto | 70 +++++++++---------- DysonNetwork.Shared/Registry/ServiceHelper.cs | 8 +-- 9 files changed, 86 insertions(+), 57 deletions(-) diff --git a/DysonNetwork.Drive/Program.cs b/DysonNetwork.Drive/Program.cs index 480ed64..d538e61 100644 --- a/DysonNetwork.Drive/Program.cs +++ b/DysonNetwork.Drive/Program.cs @@ -4,6 +4,7 @@ using DysonNetwork.Shared.Auth; using DysonNetwork.Shared.Http; using DysonNetwork.Shared.Registry; using Microsoft.EntityFrameworkCore; +using tusdotnet.Stores; var builder = WebApplication.CreateBuilder(args); @@ -18,6 +19,8 @@ builder.Services.AddAppAuthentication(); builder.Services.AddAppSwagger(); builder.Services.AddDysonAuth(builder.Configuration); +builder.Services.AddAppFileStorage(builder.Configuration); + // Add flush handlers and websocket handlers builder.Services.AddAppFlushHandlers(); @@ -36,8 +39,10 @@ using (var scope = app.Services.CreateScope()) await db.Database.MigrateAsync(); } +var tusDiskStore = app.Services.GetRequiredService(); + // Configure application middleware pipeline -app.ConfigureAppMiddleware(builder.Configuration); +app.ConfigureAppMiddleware(tusDiskStore); // Configure gRPC app.ConfigureGrpcServices(); diff --git a/DysonNetwork.Drive/Startup/ApplicationBuilderExtensions.cs b/DysonNetwork.Drive/Startup/ApplicationBuilderExtensions.cs index 7504fbe..698dabe 100644 --- a/DysonNetwork.Drive/Startup/ApplicationBuilderExtensions.cs +++ b/DysonNetwork.Drive/Startup/ApplicationBuilderExtensions.cs @@ -1,8 +1,12 @@ +using DysonNetwork.Drive.Storage; +using tusdotnet; +using tusdotnet.Interfaces; + namespace DysonNetwork.Drive.Startup; public static class ApplicationBuilderExtensions { - public static WebApplication ConfigureAppMiddleware(this WebApplication app, IConfiguration configuration) + public static WebApplication ConfigureAppMiddleware(this WebApplication app, ITusStore tusStore) { // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) @@ -14,6 +18,8 @@ public static class ApplicationBuilderExtensions app.UseHttpsRedirection(); app.UseAuthorization(); app.MapControllers(); + + app.MapTus("/tus", _ => Task.FromResult(TusService.BuildConfiguration(tusStore))); return app; } @@ -21,7 +27,8 @@ public static class ApplicationBuilderExtensions public static WebApplication ConfigureGrpcServices(this WebApplication app) { // Map your gRPC services here - // Example: app.MapGrpcService(); + app.MapGrpcService(); + app.MapGrpcService(); return app; } diff --git a/DysonNetwork.Drive/Startup/ServiceCollectionExtensions.cs b/DysonNetwork.Drive/Startup/ServiceCollectionExtensions.cs index fba75d1..c848be3 100644 --- a/DysonNetwork.Drive/Startup/ServiceCollectionExtensions.cs +++ b/DysonNetwork.Drive/Startup/ServiceCollectionExtensions.cs @@ -1,6 +1,5 @@ using System.Text.Json; using System.Threading.RateLimiting; -using dotnet_etcd.interfaces; using DysonNetwork.Shared.Cache; using Microsoft.AspNetCore.RateLimiting; using Microsoft.OpenApi.Models; @@ -8,6 +7,7 @@ using NodaTime; using NodaTime.Serialization.SystemTextJson; using StackExchange.Redis; using DysonNetwork.Shared.Proto; +using tusdotnet.Stores; namespace DysonNetwork.Drive.Startup; @@ -122,10 +122,23 @@ public static class ServiceCollectionExtensions return services; } + + public static IServiceCollection AddAppFileStorage(this IServiceCollection services, IConfiguration configuration) + { + var tusStorePath = configuration.GetSection("Tus").GetValue("StorePath")!; + Directory.CreateDirectory(tusStorePath); + var tusDiskStore = new TusDiskStore(tusStorePath); + + services.AddSingleton(tusDiskStore); + + return services; + } public static IServiceCollection AddAppBusinessServices(this IServiceCollection services) { - // Add your business services here + services.AddScoped(); + services.AddScoped(); + return services; } } \ No newline at end of file diff --git a/DysonNetwork.Drive/Storage/FileReferenceServiceGrpc.cs b/DysonNetwork.Drive/Storage/FileReferenceServiceGrpc.cs index 758efe2..d31a8c3 100644 --- a/DysonNetwork.Drive/Storage/FileReferenceServiceGrpc.cs +++ b/DysonNetwork.Drive/Storage/FileReferenceServiceGrpc.cs @@ -1,6 +1,4 @@ -using System.Threading.Tasks; using DysonNetwork.Shared.Proto; -using Google.Protobuf.WellKnownTypes; using Grpc.Core; using NodaTime; using Duration = NodaTime.Duration; @@ -13,13 +11,9 @@ namespace DysonNetwork.Drive.Storage { Instant? expiredAt = null; if (request.ExpiredAt != null) - { expiredAt = Instant.FromUnixTimeSeconds(request.ExpiredAt.Seconds); - } else if (request.Duration != null) - { expiredAt = SystemClock.Instance.GetCurrentInstant() + Duration.FromTimeSpan(request.Duration.ToTimeSpan()); - } var reference = await fileReferenceService.CreateReferenceAsync( request.FileId, diff --git a/DysonNetwork.Drive/Storage/FileServiceGrpc.cs b/DysonNetwork.Drive/Storage/FileServiceGrpc.cs index ea1e735..ef80f5c 100644 --- a/DysonNetwork.Drive/Storage/FileServiceGrpc.cs +++ b/DysonNetwork.Drive/Storage/FileServiceGrpc.cs @@ -1,4 +1,3 @@ -using System.Threading.Tasks; using DysonNetwork.Shared.Proto; using Google.Protobuf.WellKnownTypes; using Grpc.Core; diff --git a/DysonNetwork.Pass/Account/AccountEventService.cs b/DysonNetwork.Pass/Account/AccountEventService.cs index 234b29b..5fe756b 100644 --- a/DysonNetwork.Pass/Account/AccountEventService.cs +++ b/DysonNetwork.Pass/Account/AccountEventService.cs @@ -1,6 +1,7 @@ using System.Globalization; using DysonNetwork.Pass.Wallet; using DysonNetwork.Shared.Cache; +using DysonNetwork.Shared.Proto; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Localization; using NodaTime; @@ -11,11 +12,20 @@ public class AccountEventService( AppDatabase db, PaymentService payment, ICacheService cache, - IStringLocalizer localizer + IStringLocalizer localizer, + PusherService.PusherServiceClient pusher ) { private static readonly Random Random = new(); - private const string StatusCacheKey = "AccountStatus_"; + private const string StatusCacheKey = "account:status:"; + + private async Task GetAccountIsConnected(Guid userId) + { + var resp = await pusher.GetWebsocketConnectionStatusAsync( + new GetWebsocketConnectionStatusRequest { UserId = userId.ToString() } + ); + return resp.IsConnected; + } public void PurgeStatusCache(Guid userId) { @@ -29,7 +39,7 @@ public class AccountEventService( var cachedStatus = await cache.GetAsync(cacheKey); if (cachedStatus is not null) { - cachedStatus!.IsOnline = !cachedStatus.IsInvisible; // && ws.GetAccountIsConnected(userId); + cachedStatus!.IsOnline = !cachedStatus.IsInvisible && await GetAccountIsConnected(userId); return cachedStatus; } @@ -81,7 +91,7 @@ public class AccountEventService( var cachedStatus = await cache.GetAsync(cacheKey); if (cachedStatus != null) { - cachedStatus.IsOnline = !cachedStatus.IsInvisible /* && ws.GetAccountIsConnected(userId) */; + cachedStatus.IsOnline = !cachedStatus.IsInvisible && await GetAccountIsConnected(userId); results[userId] = cachedStatus; } else @@ -104,7 +114,7 @@ public class AccountEventService( foreach (var status in statusesFromDb) { - var isOnline = false; // ws.GetAccountIsConnected(status.AccountId); + var isOnline = await GetAccountIsConnected(status.AccountId); status.IsOnline = !status.IsInvisible && isOnline; results[status.AccountId] = status; var cacheKey = $"{StatusCacheKey}{status.AccountId}"; @@ -117,7 +127,7 @@ public class AccountEventService( { foreach (var userId in usersWithoutStatus) { - var isOnline = false; // ws.GetAccountIsConnected(userId); + var isOnline = await GetAccountIsConnected(userId); var defaultStatus = new Status { Attitude = StatusAttitude.Neutral, diff --git a/DysonNetwork.Pass/Program.cs b/DysonNetwork.Pass/Program.cs index 1dfa7d2..56c449e 100644 --- a/DysonNetwork.Pass/Program.cs +++ b/DysonNetwork.Pass/Program.cs @@ -15,6 +15,7 @@ builder.Services.AddAppMetrics(); // Add application services builder.Services.AddRegistryService(builder.Configuration); +builder.Services.AddPusherService(); builder.Services.AddAppServices(builder.Configuration); builder.Services.AddAppRateLimiting(); builder.Services.AddAppAuthentication(); diff --git a/DysonNetwork.Shared/Proto/file.proto b/DysonNetwork.Shared/Proto/file.proto index 92cd6f7..515e16f 100644 --- a/DysonNetwork.Shared/Proto/file.proto +++ b/DysonNetwork.Shared/Proto/file.proto @@ -15,34 +15,34 @@ import "google/protobuf/duration.proto"; message CloudFile { // Unique identifier for the file string id = 1; - + // Original name of the file string name = 2; - + // File metadata (e.g., dimensions, duration, etc.) map file_meta = 3; - + // User-defined metadata map user_meta = 4; - + // MIME type of the file string mime_type = 5; - + // File content hash (e.g., MD5, SHA-256) string hash = 6; - + // File size in bytes int64 size = 7; - + // Indicates if the file is stored with compression bool has_compression = 8; - + // URL to access the file string url = 9; - + // Content type of the file string content_type = 10; - + // When the file was uploaded google.protobuf.Timestamp uploaded_at = 11; } @@ -51,28 +51,28 @@ message CloudFile { service FileService { // Get file reference by ID rpc GetFile(GetFileRequest) returns (CloudFile); - + // Update an existing file reference rpc UpdateFile(UpdateFileRequest) returns (CloudFile); - + // Delete a file reference rpc DeleteFile(DeleteFileRequest) returns (google.protobuf.Empty); - + // Process and upload a new file rpc ProcessNewFile(stream ProcessNewFileRequest) returns (CloudFile); - + // Upload a file to remote storage rpc UploadFileToRemote(stream UploadFileToRemoteRequest) returns (CloudFile); - + // Delete file data from storage rpc DeleteFileData(DeleteFileDataRequest) returns (google.protobuf.Empty); - + // Load files from references rpc LoadFromReference(LoadFromReferenceRequest) returns (LoadFromReferenceResponse); - + // Check if a file is referenced by any resource rpc IsReferenced(IsReferencedRequest) returns (IsReferencedResponse); - + // Purge cache for a file rpc PurgeCache(PurgeCacheRequest) returns (google.protobuf.Empty); } @@ -160,19 +160,19 @@ message PurgeCacheRequest { message CloudFileReference { // Unique identifier for the reference string id = 1; - + // Reference to the actual file string file_id = 2; - + // The actual file data (optional, can be populated when needed) CloudFile file = 3; - + // Description of how this file is being used string usage = 4; - + // ID of the resource that this file is associated with string resource_id = 5; - + // Optional expiration timestamp for the reference google.protobuf.Timestamp expired_at = 6; } @@ -182,8 +182,8 @@ message CreateReferenceRequest { string file_id = 1; string usage = 2; string resource_id = 3; - google.protobuf.Timestamp expired_at = 4; - google.protobuf.Duration duration = 5; // Alternative to expired_at + optional google.protobuf.Timestamp expired_at = 4; + optional google.protobuf.Duration duration = 5; // Alternative to expired_at } message GetReferencesRequest { @@ -268,34 +268,34 @@ message HasFileReferencesResponse { service FileReferenceService { // Creates a new reference to a file for a specific resource rpc CreateReference(CreateReferenceRequest) returns (CloudFileReference); - + // Gets all references to a file rpc GetReferences(GetReferencesRequest) returns (GetReferencesResponse); - + // Gets the number of references to a file rpc GetReferenceCount(GetReferenceCountRequest) returns (GetReferenceCountResponse); - + // Gets all references for a specific resource and optional usage rpc GetResourceReferences(GetResourceReferencesRequest) returns (GetReferencesResponse); - + // Gets all files referenced by a resource with optional usage filter rpc GetResourceFiles(GetResourceFilesRequest) returns (GetResourceFilesResponse); - + // Deletes references for a specific resource and optional usage rpc DeleteResourceReferences(DeleteResourceReferencesRequest) returns (DeleteResourceReferencesResponse); - + // Deletes a specific file reference rpc DeleteReference(DeleteReferenceRequest) returns (DeleteReferenceResponse); - + // Updates the files referenced by a resource rpc UpdateResourceFiles(UpdateResourceFilesRequest) returns (UpdateResourceFilesResponse); - + // Updates the expiration time for a file reference rpc SetReferenceExpiration(SetReferenceExpirationRequest) returns (SetReferenceExpirationResponse); - + // Updates the expiration time for all references to a file rpc SetFileReferencesExpiration(SetFileReferencesExpirationRequest) returns (SetFileReferencesExpirationResponse); - + // Checks if a file has any references rpc HasFileReferences(HasFileReferencesRequest) returns (HasFileReferencesResponse); } diff --git a/DysonNetwork.Shared/Registry/ServiceHelper.cs b/DysonNetwork.Shared/Registry/ServiceHelper.cs index 47353ee..6b2f18b 100644 --- a/DysonNetwork.Shared/Registry/ServiceHelper.cs +++ b/DysonNetwork.Shared/Registry/ServiceHelper.cs @@ -13,8 +13,8 @@ public static class ServiceHelper { var etcdClient = sp.GetRequiredService(); var config = sp.GetRequiredService(); - var clientCertPath = config["Service:ClientCert"]; - var clientKeyPath = config["Service:ClientKey"]; + var clientCertPath = config["Service:ClientCert"]!; + var clientKeyPath = config["Service:ClientKey"]!; var clientCertPassword = config["Service:CertPassword"]; return GrpcClientHelper @@ -32,8 +32,8 @@ public static class ServiceHelper { var etcdClient = sp.GetRequiredService(); var config = sp.GetRequiredService(); - var clientCertPath = config["Service:ClientCert"]; - var clientKeyPath = config["Service:ClientKey"]; + var clientCertPath = config["Service:ClientCert"]!; + var clientKeyPath = config["Service:ClientKey"]!; var clientCertPassword = config["Service:CertPassword"]; return GrpcClientHelper From ef9175d27d5020e67d6d546f0f54b0768d47e30f Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Mon, 14 Jul 2025 13:50:41 +0800 Subject: [PATCH 16/42] :sparkles: Remix file service --- DysonNetwork.Drive/Storage/CloudFile.cs | 4 +- .../Storage/FileReferenceServiceGrpc.cs | 55 ++++++--- DysonNetwork.Drive/Storage/FileServiceGrpc.cs | 114 +----------------- DysonNetwork.Pass/Account/Account.cs | 10 +- .../Account/AccountController.cs | 5 - .../Account/AccountCurrentController.cs | 41 ++++++- .../Account/AccountEventService.cs | 2 +- .../Data/CloudFileReferenceObject.cs | 22 +++- DysonNetwork.Shared/Data/ICloudFile.cs | 2 +- DysonNetwork.Shared/Proto/GrpcTypeHelper.cs | 2 +- DysonNetwork.Shared/Proto/account.proto | 4 - DysonNetwork.Shared/Proto/file.proto | 33 +---- 12 files changed, 110 insertions(+), 184 deletions(-) diff --git a/DysonNetwork.Drive/Storage/CloudFile.cs b/DysonNetwork.Drive/Storage/CloudFile.cs index 6911dc2..81d9239 100644 --- a/DysonNetwork.Drive/Storage/CloudFile.cs +++ b/DysonNetwork.Drive/Storage/CloudFile.cs @@ -29,7 +29,7 @@ public class CloudFileReferenceObject : ModelBase, ICloudFile { public string Id { get; set; } = null!; public string Name { get; set; } = string.Empty; - public Dictionary? FileMeta { get; set; } = null!; + public Dictionary FileMeta { get; set; } = null!; public Dictionary? UserMeta { get; set; } = null!; public string? MimeType { get; set; } public string? Hash { get; set; } @@ -45,7 +45,7 @@ public class CloudFile : ModelBase, ICloudFile, IIdentifiedResource [MaxLength(1024)] public string Name { get; set; } = string.Empty; [MaxLength(4096)] public string? Description { get; set; } - [Column(TypeName = "jsonb")] public Dictionary? FileMeta { get; set; } = null!; + [Column(TypeName = "jsonb")] public Dictionary FileMeta { get; set; } = null!; [Column(TypeName = "jsonb")] public Dictionary? UserMeta { get; set; } = null!; [Column(TypeName = "jsonb")] public List? SensitiveMarks { get; set; } = []; [MaxLength(256)] public string? MimeType { get; set; } diff --git a/DysonNetwork.Drive/Storage/FileReferenceServiceGrpc.cs b/DysonNetwork.Drive/Storage/FileReferenceServiceGrpc.cs index d31a8c3..2a1459e 100644 --- a/DysonNetwork.Drive/Storage/FileReferenceServiceGrpc.cs +++ b/DysonNetwork.Drive/Storage/FileReferenceServiceGrpc.cs @@ -5,15 +5,18 @@ using Duration = NodaTime.Duration; namespace DysonNetwork.Drive.Storage { - public class FileReferenceServiceGrpc(FileReferenceService fileReferenceService) : Shared.Proto.FileReferenceService.FileReferenceServiceBase + public class FileReferenceServiceGrpc(FileReferenceService fileReferenceService) + : Shared.Proto.FileReferenceService.FileReferenceServiceBase { - public override async Task CreateReference(CreateReferenceRequest request, ServerCallContext context) + public override async Task CreateReference(CreateReferenceRequest request, + ServerCallContext context) { Instant? expiredAt = null; if (request.ExpiredAt != null) expiredAt = Instant.FromUnixTimeSeconds(request.ExpiredAt.Seconds); else if (request.Duration != null) - expiredAt = SystemClock.Instance.GetCurrentInstant() + Duration.FromTimeSpan(request.Duration.ToTimeSpan()); + expiredAt = SystemClock.Instance.GetCurrentInstant() + + Duration.FromTimeSpan(request.Duration.ToTimeSpan()); var reference = await fileReferenceService.CreateReferenceAsync( request.FileId, @@ -24,7 +27,8 @@ namespace DysonNetwork.Drive.Storage return reference.ToProtoValue(); } - public override async Task GetReferences(GetReferencesRequest request, ServerCallContext context) + public override async Task GetReferences(GetReferencesRequest request, + ServerCallContext context) { var references = await fileReferenceService.GetReferencesAsync(request.FileId); var response = new GetReferencesResponse(); @@ -32,13 +36,15 @@ namespace DysonNetwork.Drive.Storage return response; } - public override async Task GetReferenceCount(GetReferenceCountRequest request, ServerCallContext context) + public override async Task GetReferenceCount(GetReferenceCountRequest request, + ServerCallContext context) { var count = await fileReferenceService.GetReferenceCountAsync(request.FileId); return new GetReferenceCountResponse { Count = count }; } - public override async Task GetResourceReferences(GetResourceReferencesRequest request, ServerCallContext context) + public override async Task GetResourceReferences(GetResourceReferencesRequest request, + ServerCallContext context) { var references = await fileReferenceService.GetResourceReferencesAsync(request.ResourceId, request.Usage); var response = new GetReferencesResponse(); @@ -46,7 +52,8 @@ namespace DysonNetwork.Drive.Storage return response; } - public override async Task GetResourceFiles(GetResourceFilesRequest request, ServerCallContext context) + public override async Task GetResourceFiles(GetResourceFilesRequest request, + ServerCallContext context) { var files = await fileReferenceService.GetResourceFilesAsync(request.ResourceId, request.Usage); var response = new GetResourceFilesResponse(); @@ -54,19 +61,27 @@ namespace DysonNetwork.Drive.Storage return response; } - public override async Task DeleteResourceReferences(DeleteResourceReferencesRequest request, ServerCallContext context) + public override async Task DeleteResourceReferences( + DeleteResourceReferencesRequest request, ServerCallContext context) { - var deletedCount = await fileReferenceService.DeleteResourceReferencesAsync(request.ResourceId, request.Usage); + var deletedCount = 0; + if (request.Usage is null) + deletedCount = await fileReferenceService.DeleteResourceReferencesAsync(request.ResourceId); + else + deletedCount = + await fileReferenceService.DeleteResourceReferencesAsync(request.ResourceId, request.Usage!); return new DeleteResourceReferencesResponse { DeletedCount = deletedCount }; } - public override async Task DeleteReference(DeleteReferenceRequest request, ServerCallContext context) + public override async Task DeleteReference(DeleteReferenceRequest request, + ServerCallContext context) { var success = await fileReferenceService.DeleteReferenceAsync(Guid.Parse(request.ReferenceId)); return new DeleteReferenceResponse { Success = success }; } - public override async Task UpdateResourceFiles(UpdateResourceFilesRequest request, ServerCallContext context) + public override async Task UpdateResourceFiles(UpdateResourceFilesRequest request, + ServerCallContext context) { Instant? expiredAt = null; if (request.ExpiredAt != null) @@ -75,7 +90,8 @@ namespace DysonNetwork.Drive.Storage } else if (request.Duration != null) { - expiredAt = SystemClock.Instance.GetCurrentInstant() + Duration.FromTimeSpan(request.Duration.ToTimeSpan()); + expiredAt = SystemClock.Instance.GetCurrentInstant() + + Duration.FromTimeSpan(request.Duration.ToTimeSpan()); } var references = await fileReferenceService.UpdateResourceFilesAsync( @@ -89,7 +105,8 @@ namespace DysonNetwork.Drive.Storage return response; } - public override async Task SetReferenceExpiration(SetReferenceExpirationRequest request, ServerCallContext context) + public override async Task SetReferenceExpiration( + SetReferenceExpirationRequest request, ServerCallContext context) { Instant? expiredAt = null; if (request.ExpiredAt != null) @@ -98,21 +115,25 @@ namespace DysonNetwork.Drive.Storage } else if (request.Duration != null) { - expiredAt = SystemClock.Instance.GetCurrentInstant() + Duration.FromTimeSpan(request.Duration.ToTimeSpan()); + expiredAt = SystemClock.Instance.GetCurrentInstant() + + Duration.FromTimeSpan(request.Duration.ToTimeSpan()); } - var success = await fileReferenceService.SetReferenceExpirationAsync(Guid.Parse(request.ReferenceId), expiredAt); + var success = + await fileReferenceService.SetReferenceExpirationAsync(Guid.Parse(request.ReferenceId), expiredAt); return new SetReferenceExpirationResponse { Success = success }; } - public override async Task SetFileReferencesExpiration(SetFileReferencesExpirationRequest request, ServerCallContext context) + public override async Task SetFileReferencesExpiration( + SetFileReferencesExpirationRequest request, ServerCallContext context) { var expiredAt = Instant.FromUnixTimeSeconds(request.ExpiredAt.Seconds); var updatedCount = await fileReferenceService.SetFileReferencesExpirationAsync(request.FileId, expiredAt); return new SetFileReferencesExpirationResponse { UpdatedCount = updatedCount }; } - public override async Task HasFileReferences(HasFileReferencesRequest request, ServerCallContext context) + public override async Task HasFileReferences(HasFileReferencesRequest request, + ServerCallContext context) { var hasReferences = await fileReferenceService.HasFileReferencesAsync(request.FileId); return new HasFileReferencesResponse { HasReferences = hasReferences }; diff --git a/DysonNetwork.Drive/Storage/FileServiceGrpc.cs b/DysonNetwork.Drive/Storage/FileServiceGrpc.cs index ef80f5c..4b62afb 100644 --- a/DysonNetwork.Drive/Storage/FileServiceGrpc.cs +++ b/DysonNetwork.Drive/Storage/FileServiceGrpc.cs @@ -12,7 +12,8 @@ namespace DysonNetwork.Drive.Storage return file?.ToProtoValue() ?? throw new RpcException(new Status(StatusCode.NotFound, "File not found")); } - public override async Task UpdateFile(UpdateFileRequest request, ServerCallContext context) + public override async Task UpdateFile(UpdateFileRequest request, + ServerCallContext context) { var file = await fileService.GetFileAsync(request.File.Id); if (file == null) @@ -33,113 +34,10 @@ namespace DysonNetwork.Drive.Storage return new Empty(); } - public override async Task ProcessNewFile(IAsyncStreamReader requestStream, - ServerCallContext context) - { - ProcessNewFileRequest? metadataRequest = null; - var chunks = new List(); - - await foreach (var message in requestStream.ReadAllAsync()) - { - if (message.DataCase == ProcessNewFileRequest.DataOneofCase.Metadata) - { - metadataRequest = message; - } - else if (message.DataCase == ProcessNewFileRequest.DataOneofCase.Chunk) - { - chunks.Add(message.Chunk.ToByteArray()); - } - } - - if (metadataRequest == null || metadataRequest.Metadata == null) - { - throw new RpcException(new Status(StatusCode.InvalidArgument, "Missing file metadata")); - } - - var metadata = metadataRequest.Metadata; - using var memoryStream = new MemoryStream(); - foreach (var chunk in chunks) - { - await memoryStream.WriteAsync(chunk); - } - - memoryStream.Position = 0; - - // Assuming you have an Account object available or can create a dummy one for now - // You might need to adjust this based on how accounts are handled in your system - var dummyAccount = new Account { Id = metadata.AccountId }; - - var cloudFile = await fileService.ProcessNewFileAsync( - dummyAccount, - metadata.FileId, - memoryStream, - metadata.FileName, - metadata.ContentType - ); - return cloudFile.ToProtoValue(); - } - - public override async Task UploadFileToRemote( - IAsyncStreamReader requestStream, ServerCallContext context) - { - UploadFileToRemoteRequest? metadataRequest = null; - var chunks = new List(); - - await foreach (var message in requestStream.ReadAllAsync()) - { - if (message.DataCase == UploadFileToRemoteRequest.DataOneofCase.Metadata) - { - metadataRequest = message; - } - else if (message.DataCase == UploadFileToRemoteRequest.DataOneofCase.Chunk) - { - chunks.Add(message.Chunk.ToByteArray()); - } - } - - if (metadataRequest == null || metadataRequest.Metadata == null) - { - throw new RpcException(new Status(StatusCode.InvalidArgument, "Missing upload metadata")); - } - - var metadata = metadataRequest.Metadata; - using var memoryStream = new MemoryStream(); - foreach (var chunk in chunks) - { - await memoryStream.WriteAsync(chunk); - } - - memoryStream.Position = 0; - - var file = await fileService.GetFileAsync(metadata.FileId); - if (file == null) - { - throw new RpcException(new Status(StatusCode.NotFound, "File not found")); - } - - var uploadedFile = await fileService.UploadFileToRemoteAsync( - file, - memoryStream, - metadata.TargetRemote, - metadata.Suffix - ); - return uploadedFile.ToProtoValue(); - } - - public override async Task DeleteFileData(DeleteFileDataRequest request, ServerCallContext context) - { - var file = await fileService.GetFileAsync(request.FileId); - if (file == null) - { - throw new RpcException(new Status(StatusCode.NotFound, "File not found")); - } - - await fileService.DeleteFileDataAsync(file); - return new Empty(); - } - - public override async Task LoadFromReference(LoadFromReferenceRequest request, - ServerCallContext context) + public override async Task LoadFromReference( + LoadFromReferenceRequest request, + ServerCallContext context + ) { // Assuming CloudFileReferenceObject is a simple class/struct that holds an ID // You might need to define this or adjust the LoadFromReference method in FileService diff --git a/DysonNetwork.Pass/Account/Account.cs b/DysonNetwork.Pass/Account/Account.cs index 3078c7c..7015c9a 100644 --- a/DysonNetwork.Pass/Account/Account.cs +++ b/DysonNetwork.Pass/Account/Account.cs @@ -78,7 +78,7 @@ public abstract class Leveling ]; } -public class AccountProfile : ModelBase +public class AccountProfile : ModelBase, IIdentifiedResource { public Guid Id { get; set; } [MaxLength(256)] public string? FirstName { get; set; } @@ -104,10 +104,6 @@ public class AccountProfile : ModelBase : (Experience - Leveling.ExperiencePerLevel[Level]) * 100.0 / (Leveling.ExperiencePerLevel[Level + 1] - Leveling.ExperiencePerLevel[Level]); - // Outdated fields, for backward compability - [MaxLength(32)] public string? PictureId { get; set; } - [MaxLength(32)] public string? BackgroundId { get; set; } - [Column(TypeName = "jsonb")] public CloudFileReferenceObject? Picture { get; set; } [Column(TypeName = "jsonb")] public CloudFileReferenceObject? Background { get; set; } @@ -132,8 +128,6 @@ public class AccountProfile : ModelBase Experience = Experience, Level = Level, LevelingProgress = LevelingProgress, - PictureId = PictureId ?? string.Empty, - BackgroundId = BackgroundId ?? string.Empty, Picture = Picture?.ToProtoValue(), Background = Background?.ToProtoValue(), AccountId = AccountId.ToString(), @@ -143,6 +137,8 @@ public class AccountProfile : ModelBase return proto; } + + public string ResourceIdentifier => $"account:profile:{Id}"; } public class AccountContact : ModelBase diff --git a/DysonNetwork.Pass/Account/AccountController.cs b/DysonNetwork.Pass/Account/AccountController.cs index 98a2564..ee2054b 100644 --- a/DysonNetwork.Pass/Account/AccountController.cs +++ b/DysonNetwork.Pass/Account/AccountController.cs @@ -1,13 +1,8 @@ using System.ComponentModel.DataAnnotations; using DysonNetwork.Pass.Auth; -using DysonNetwork.Pass; -using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using NodaTime; -using NodaTime.Extensions; -using System.Collections.Generic; -using DysonNetwork.Pass.Account; namespace DysonNetwork.Pass.Account; diff --git a/DysonNetwork.Pass/Account/AccountCurrentController.cs b/DysonNetwork.Pass/Account/AccountCurrentController.cs index f5e830a..ea5f57d 100644 --- a/DysonNetwork.Pass/Account/AccountCurrentController.cs +++ b/DysonNetwork.Pass/Account/AccountCurrentController.cs @@ -1,10 +1,14 @@ using System.ComponentModel.DataAnnotations; -using DysonNetwork.Pass.Auth; using DysonNetwork.Pass.Permission; +using DysonNetwork.Shared.Data; +using DysonNetwork.Shared.Proto; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using NodaTime; +using AuthService = DysonNetwork.Pass.Auth.AuthService; +using AuthSession = DysonNetwork.Pass.Auth.AuthSession; +using ChallengePlatform = DysonNetwork.Pass.Auth.ChallengePlatform; namespace DysonNetwork.Pass.Account; @@ -15,7 +19,9 @@ public class AccountCurrentController( AppDatabase db, AccountService accounts, AccountEventService events, - AuthService auth + AuthService auth, + FileService.FileServiceClient files, + FileReferenceService.FileReferenceServiceClient fileRefs ) : ControllerBase { [HttpGet] @@ -94,12 +100,37 @@ public class AccountCurrentController( if (request.PictureId is not null) { - // TODO: Create reference, set profile picture + var file = await files.GetFileAsync(new GetFileRequest { Id = request.PictureId }); + if (profile.Picture is not null) + await fileRefs.DeleteResourceReferencesAsync( + new DeleteResourceReferencesRequest { ResourceId = profile.ResourceIdentifier } + ); + await fileRefs.CreateReferenceAsync( + new CreateReferenceRequest + { + ResourceId = profile.ResourceIdentifier, + FileId = request.PictureId, + Usage = "profile.picture" + } + ); + profile.Picture = CloudFileReferenceObject.FromProtoValue(file); } - if (request.BackgroundId is not null) { - // TODO: Create reference, set profile background + var file = await files.GetFileAsync(new GetFileRequest { Id = request.BackgroundId }); + if (profile.Background is not null) + await fileRefs.DeleteResourceReferencesAsync( + new DeleteResourceReferencesRequest { ResourceId = profile.ResourceIdentifier } + ); + await fileRefs.CreateReferenceAsync( + new CreateReferenceRequest + { + ResourceId = profile.ResourceIdentifier, + FileId = request.BackgroundId, + Usage = "profile.background" + } + ); + profile.Background = CloudFileReferenceObject.FromProtoValue(file); } db.Update(profile); diff --git a/DysonNetwork.Pass/Account/AccountEventService.cs b/DysonNetwork.Pass/Account/AccountEventService.cs index 5fe756b..8040e7a 100644 --- a/DysonNetwork.Pass/Account/AccountEventService.cs +++ b/DysonNetwork.Pass/Account/AccountEventService.cs @@ -100,7 +100,7 @@ public class AccountEventService( } } - if (cacheMissUserIds.Any()) + if (cacheMissUserIds.Count != 0) { var now = SystemClock.Instance.GetCurrentInstant(); var statusesFromDb = await db.AccountStatuses diff --git a/DysonNetwork.Shared/Data/CloudFileReferenceObject.cs b/DysonNetwork.Shared/Data/CloudFileReferenceObject.cs index b20826f..4866115 100644 --- a/DysonNetwork.Shared/Data/CloudFileReferenceObject.cs +++ b/DysonNetwork.Shared/Data/CloudFileReferenceObject.cs @@ -1,3 +1,4 @@ +using DysonNetwork.Shared.Proto; using Google.Protobuf.WellKnownTypes; namespace DysonNetwork.Shared.Data; @@ -10,13 +11,30 @@ public class CloudFileReferenceObject : ModelBase, ICloudFile { public string Id { get; set; } = null!; public string Name { get; set; } = string.Empty; - public Dictionary? FileMeta { get; set; } = null!; - public Dictionary? UserMeta { get; set; } = null!; + public Dictionary FileMeta { get; set; } = null!; + public Dictionary UserMeta { get; set; } = null!; public string? MimeType { get; set; } public string? Hash { get; set; } public long Size { get; set; } public bool HasCompression { get; set; } = false; + public static CloudFileReferenceObject FromProtoValue(Proto.CloudFile proto) + { + return new CloudFileReferenceObject + { + Id = proto.Id, + Name = proto.Name, + FileMeta = proto.FileMeta + .ToDictionary(kvp => kvp.Key, kvp => GrpcTypeHelper.ConvertField(kvp.Value)), + UserMeta = proto.UserMeta + .ToDictionary(kvp => kvp.Key, kvp => GrpcTypeHelper.ConvertField(kvp.Value)), + MimeType = proto.MimeType, + Hash = proto.Hash, + Size = proto.Size, + HasCompression = proto.HasCompression + }; + } + /// /// Converts the current object to its protobuf representation /// diff --git a/DysonNetwork.Shared/Data/ICloudFile.cs b/DysonNetwork.Shared/Data/ICloudFile.cs index 35543a3..68c25f3 100644 --- a/DysonNetwork.Shared/Data/ICloudFile.cs +++ b/DysonNetwork.Shared/Data/ICloudFile.cs @@ -26,7 +26,7 @@ public interface ICloudFile /// /// Gets the file metadata dictionary. /// - Dictionary? FileMeta { get; } + Dictionary FileMeta { get; } /// /// Gets the user metadata dictionary. diff --git a/DysonNetwork.Shared/Proto/GrpcTypeHelper.cs b/DysonNetwork.Shared/Proto/GrpcTypeHelper.cs index 1d2d7bd..d81d092 100644 --- a/DysonNetwork.Shared/Proto/GrpcTypeHelper.cs +++ b/DysonNetwork.Shared/Proto/GrpcTypeHelper.cs @@ -75,7 +75,7 @@ public abstract class GrpcTypeHelper return result; } - private static object? ConvertField(Value value) + public static object? ConvertField(Value value) { return value.KindCase switch { diff --git a/DysonNetwork.Shared/Proto/account.proto b/DysonNetwork.Shared/Proto/account.proto index 59afef5..dd332e6 100644 --- a/DysonNetwork.Shared/Proto/account.proto +++ b/DysonNetwork.Shared/Proto/account.proto @@ -51,10 +51,6 @@ message AccountProfile { int32 level = 15; double leveling_progress = 16; - // Legacy fields - google.protobuf.StringValue picture_id = 17; - google.protobuf.StringValue background_id = 18; - CloudFile picture = 19; CloudFile background = 20; diff --git a/DysonNetwork.Shared/Proto/file.proto b/DysonNetwork.Shared/Proto/file.proto index 515e16f..8ba4d8e 100644 --- a/DysonNetwork.Shared/Proto/file.proto +++ b/DysonNetwork.Shared/Proto/file.proto @@ -58,15 +58,6 @@ service FileService { // Delete a file reference rpc DeleteFile(DeleteFileRequest) returns (google.protobuf.Empty); - // Process and upload a new file - rpc ProcessNewFile(stream ProcessNewFileRequest) returns (CloudFile); - - // Upload a file to remote storage - rpc UploadFileToRemote(stream UploadFileToRemoteRequest) returns (CloudFile); - - // Delete file data from storage - rpc DeleteFileData(DeleteFileDataRequest) returns (google.protobuf.Empty); - // Load files from references rpc LoadFromReference(LoadFromReferenceRequest) returns (LoadFromReferenceResponse); @@ -88,14 +79,6 @@ message UpdateFileRequest { google.protobuf.FieldMask update_mask = 2; } -// Request message for DeleteFile -message ProcessNewFileRequest { - oneof data { - FileMetadata metadata = 1; - bytes chunk = 2; - } -} - message FileMetadata { string file_id = 1; string file_name = 2; @@ -103,13 +86,6 @@ message FileMetadata { string account_id = 4; } -message UploadFileToRemoteRequest { - oneof data { - UploadMetadata metadata = 1; - bytes chunk = 2; - } -} - message UploadMetadata { string file_id = 1; string target_remote = 2; @@ -122,11 +98,6 @@ message DeleteFileRequest { bool purge = 2; } -message DeleteFileDataRequest { - string file_id = 1; - bool force = 2; -} - message LoadFromReferenceRequest { repeated string reference_ids = 1; } @@ -201,7 +172,7 @@ message GetResourceReferencesRequest { message GetResourceFilesRequest { string resource_id = 1; - string usage = 2; // Optional + optional string usage = 2; } message GetResourceFilesResponse { @@ -210,7 +181,7 @@ message GetResourceFilesResponse { message DeleteResourceReferencesRequest { string resource_id = 1; - string usage = 2; // Optional + optional string usage = 2; } message DeleteResourceReferencesResponse { From cbfdb4aa604c8bf6a1e03bba97bc44d0468472b8 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Mon, 14 Jul 2025 19:55:28 +0800 Subject: [PATCH 17/42] :recycle: I have no idea what am I doing. Might be mixing stuff --- ...0250713121317_InitialMigration.Designer.cs | 1 + .../20250713121317_InitialMigration.cs | 1 + .../Migrations/AppDatabaseModelSnapshot.cs | 1 + DysonNetwork.Drive/Storage/CloudFile.cs | 19 +- .../Storage/FileReferenceService.cs | 37 +- .../Storage/FileReferenceServiceGrpc.cs | 21 + DysonNetwork.Drive/Storage/FileService.cs | 47 +- DysonNetwork.Drive/Storage/FileServiceGrpc.cs | 6 + .../Account/AccountServiceGrpc.cs | 66 +- DysonNetwork.Pass/Account/Relationship.cs | 13 + .../Account/RelationshipService.cs | 18 +- DysonNetwork.Pass/Auth/AuthServiceGrpc.cs | 17 +- DysonNetwork.Pass/Developer/CustomApp.cs | 2 +- DysonNetwork.Pass/DysonNetwork.Pass.csproj | 4 + .../Pages/Checkpoint/CheckpointPage.cshtml | 4 +- .../Pages/Checkpoint/CheckpointPage.cshtml.cs | 2 +- DysonNetwork.Pass/Pages/Shared/_Layout.cshtml | 62 + .../Pages/Spell/MagicSpellPage.cshtml | 6 +- .../Pages/Spell/MagicSpellPage.cshtml.cs | 4 +- DysonNetwork.Pass/Pages/_ViewImports.cshtml | 2 + DysonNetwork.Pass/Pages/_ViewStart.cshtml | 3 + DysonNetwork.Shared/Content/TextSanitizer.cs | 40 + DysonNetwork.Shared/CultureService.cs | 19 + .../Data/CloudFileReferenceObject.cs | 17 + .../Data}/VerificationMark.cs | 2 +- DysonNetwork.Shared/Proto/GrpcClientHelper.cs | 24 + DysonNetwork.Shared/Proto/account.proto | 165 +- DysonNetwork.Shared/Proto/auth.proto | 16 + DysonNetwork.Shared/Proto/file.proto | 22 + DysonNetwork.Shared/Registry/ServiceHelper.cs | 33 + DysonNetwork.Sphere/Account/AbuseReport.cs | 30 - DysonNetwork.Sphere/Account/Account.cs | 196 - .../Account/AccountController.cs | 177 - .../Account/AccountCurrentController.cs | 703 --- .../Account/AccountEventService.cs | 339 -- DysonNetwork.Sphere/Account/AccountService.cs | 657 --- .../Account/AccountUsernameService.cs | 105 - DysonNetwork.Sphere/Account/ActionLog.cs | 58 - .../Account/ActionLogService.cs | 46 - DysonNetwork.Sphere/Account/Badge.cs | 47 - DysonNetwork.Sphere/Account/Event.cs | 65 - DysonNetwork.Sphere/Account/MagicSpell.cs | 30 - .../Account/MagicSpellController.cs | 19 - .../Account/MagicSpellService.cs | 252 -- DysonNetwork.Sphere/Account/Notification.cs | 41 - .../Account/NotificationController.cs | 166 - .../Account/NotificationService.cs | 307 -- DysonNetwork.Sphere/Account/Relationship.cs | 22 - .../Account/RelationshipController.cs | 253 -- .../Account/RelationshipService.cs | 207 - .../Account/VerificationMark.cs | 25 - .../Activity/ActivityController.cs | 3 +- .../Activity/ActivityService.cs | 9 +- DysonNetwork.Sphere/AppDatabase.cs | 80 +- DysonNetwork.Sphere/Auth/Auth.cs | 279 -- DysonNetwork.Sphere/Auth/AuthController.cs | 269 -- DysonNetwork.Sphere/Auth/AuthService.cs | 304 -- DysonNetwork.Sphere/Auth/CheckpointModel.cs | 6 - .../Auth/CompactTokenService.cs | 94 - .../Controllers/OidcProviderController.cs | 242 - .../Models/AuthorizationCodeInfo.cs | 17 - .../Options/OidcProviderOptions.cs | 36 - .../Responses/AuthorizationResponse.cs | 23 - .../OidcProvider/Responses/ErrorResponse.cs | 20 - .../OidcProvider/Responses/TokenResponse.cs | 26 - .../Services/OidcProviderService.cs | 395 -- .../Auth/OpenId/AfdianOidcService.cs | 94 - .../Auth/OpenId/AppleMobileSignInRequest.cs | 19 - .../Auth/OpenId/AppleOidcService.cs | 279 -- .../Auth/OpenId/ConnectionController.cs | 409 -- .../Auth/OpenId/DiscordOidcService.cs | 115 - .../Auth/OpenId/GitHubOidcService.cs | 127 - .../Auth/OpenId/GoogleOidcService.cs | 136 - .../Auth/OpenId/MicrosoftOidcService.cs | 124 - .../Auth/OpenId/OidcController.cs | 194 - .../Auth/OpenId/OidcService.cs | 295 -- DysonNetwork.Sphere/Auth/OpenId/OidcState.cs | 189 - .../Auth/OpenId/OidcUserInfo.cs | 49 - DysonNetwork.Sphere/Auth/Session.cs | 69 - DysonNetwork.Sphere/Chat/ChatController.cs | 59 +- DysonNetwork.Sphere/Chat/ChatRoom.cs | 9 +- .../Chat/ChatRoomController.cs | 156 +- DysonNetwork.Sphere/Chat/ChatRoomService.cs | 2 +- DysonNetwork.Sphere/Chat/ChatService.cs | 2 +- DysonNetwork.Sphere/Chat/Message.cs | 7 +- .../Chat/Realtime/IRealtimeService.cs | 2 +- .../Chat/Realtime/LivekitService.cs | 2 +- .../Chat/RealtimeCallController.cs | 8 +- .../Connection/AutoCompletionController.cs | 92 - .../Connection/ClientTypeMiddleware.cs | 42 - .../Connection/GeoIpService.cs | 56 - .../Connection/Handlers/MessageReadHandler.cs | 68 - .../Handlers/MessageTypingHandler.cs | 68 - .../Handlers/MessagesSubscribeHandler.cs | 53 - .../Handlers/MessagesUnsubscribeHandler.cs | 21 - .../Connection/IWebSocketPacketHandler.cs | 9 - .../Connection/WebSocketController.cs | 108 - .../Connection/WebSocketPacket.cs | 72 - .../Connection/WebSocketService.cs | 129 - ...8172328_AddOidcProviderSupport.Designer.cs | 4000 ---------------- .../20250628172328_AddOidcProviderSupport.cs | 139 - ...50629084150_AuthSessionWithApp.Designer.cs | 4014 ----------------- .../20250629084150_AuthSessionWithApp.cs | 49 - ...0250629123136_CustomAppsRefine.Designer.cs | 3993 ---------------- .../20250629123136_CustomAppsRefine.cs | 182 - DysonNetwork.Sphere/Developer/CustomApp.cs | 2 +- .../Developer/CustomAppController.cs | 6 +- .../Developer/DeveloperController.cs | 4 +- .../DysonNetwork.Sphere.csproj | 15 +- DysonNetwork.Sphere/Email/EmailModels.cs | 31 - DysonNetwork.Sphere/Email/EmailService.cs | 106 - .../Email/RazorViewRenderer.cs | 45 - ...0250520160525_InitialMigration.Designer.cs | 3426 -------------- .../20250520160525_InitialMigration.cs | 1992 -------- ...521142845_EnrichAccountProfile.Designer.cs | 3444 -------------- .../20250521142845_EnrichAccountProfile.cs | 61 - ...1181143_FixProfileRelationship.Designer.cs | 3449 -------------- .../20250521181143_FixProfileRelationship.cs | 52 - ...523172951_RefactorChatLastRead.Designer.cs | 3396 -------------- .../20250523172951_RefactorChatLastRead.cs | 89 - ...50524215045_UpdateRealtimeChat.Designer.cs | 3400 -------------- .../20250524215045_UpdateRealtimeChat.cs | 78 - ...2_ModifyRelationshipStatusType.Designer.cs | 3400 -------------- ...0525083412_ModifyRelationshipStatusType.cs | 34 - ...2_LimitedSizeForPictureIdOnPub.Designer.cs | 3402 -------------- ...0527144902_LimitedSizeForPictureIdOnPub.cs | 22 - ...250528171935_AddCloudFileUsage.Designer.cs | 3407 -------------- .../20250528171935_AddCloudFileUsage.cs | 29 - ...048_RefactorCloudFileReference.Designer.cs | 3394 -------------- ...250601142048_RefactorCloudFileReference.cs | 436 -- ...44445_FixPushNotificationIndex.Designer.cs | 3394 -------------- ...20250602144445_FixPushNotificationIndex.cs | 38 - ...0250603153937_BetterAuthFactor.Designer.cs | 3410 -------------- .../20250603153937_BetterAuthFactor.cs | 61 - ...100_AccountContactCanBePrimary.Designer.cs | 3414 -------------- ...250608114100_AccountContactCanBePrimary.cs | 29 - ...0250608152205_RemoveActivities.Designer.cs | 3344 -------------- .../20250608152205_RemoveActivities.cs | 55 - ...250609153232_EnrichChatMembers.Designer.cs | 3357 -------------- .../20250609153232_EnrichChatMembers.cs | 50 - ...739_ActiveBadgeAndVerification.Designer.cs | 3368 -------------- ...250610151739_ActiveBadgeAndVerification.cs | 91 - ...ingFilesAndWalletSubscriptions.Designer.cs | 3550 --------------- ...terRecyclingFilesAndWalletSubscriptions.cs | 143 - ...615083256_AddAccountConnection.Designer.cs | 3622 --------------- .../20250615083256_AddAccountConnection.cs | 54 - ...154147_EnrichAccountConnection.Designer.cs | 3626 --------------- .../20250615154147_EnrichAccountConnection.cs | 29 - ...0250621191505_WalletOrderAppDX.Designer.cs | 3633 --------------- .../20250621191505_WalletOrderAppDX.cs | 82 - ...4160304_DropActionLogSessionFk.Designer.cs | 3623 --------------- .../20250624160304_DropActionLogSessionFk.cs | 38 - ...250625150644_SafetyAbuseReport.Designer.cs | 3696 --------------- .../20250625150644_SafetyAbuseReport.cs | 66 - .../20250626093051_AddWebArticles.Designer.cs | 3852 ---------------- .../20250626093051_AddWebArticles.cs | 103 - .../20250626105203_AddRealmTags.Designer.cs | 3947 ---------------- .../Migrations/20250626105203_AddRealmTags.cs | 84 - .../Migrations/AppDatabaseModelSnapshot.cs | 3990 ---------------- .../Pages/Account/Profile.cshtml | 225 - .../Pages/Account/Profile.cshtml.cs | 28 - .../Pages/Auth/Authorize.cshtml | 113 - .../Pages/Auth/Authorize.cshtml.cs | 233 - .../Pages/Auth/Callback.cshtml | 49 - .../Pages/Auth/Callback.cshtml.cs | 11 - .../Pages/Auth/Challenge.cshtml | 16 - .../Pages/Auth/Challenge.cshtml.cs | 19 - DysonNetwork.Sphere/Pages/Auth/Login.cshtml | 40 - .../Pages/Auth/Login.cshtml.cs | 93 - .../Pages/Auth/SelectFactor.cshtml | 127 - .../Pages/Auth/SelectFactor.cshtml.cs | 103 - .../Pages/Auth/VerifyFactor.cshtml | 99 - .../Pages/Auth/VerifyFactor.cshtml.cs | 194 - .../Pages/Posts/PostDetail.cshtml | 18 +- .../Pages/Posts/PostDetail.cshtml.cs | 33 +- .../Permission/PermissionMiddleware.cs | 2 +- DysonNetwork.Sphere/Post/Post.cs | 8 +- DysonNetwork.Sphere/Post/PostController.cs | 16 +- DysonNetwork.Sphere/Post/PostService.cs | 158 +- DysonNetwork.Sphere/Publisher/Publisher.cs | 9 +- .../Publisher/PublisherController.cs | 22 +- .../Publisher/PublisherService.cs | 124 +- .../PublisherSubscriptionController.cs | 8 +- DysonNetwork.Sphere/Realm/Realm.cs | 5 +- .../Realm/RealmChatController.cs | 2 +- DysonNetwork.Sphere/Realm/RealmController.cs | 28 +- .../Safety/AbuseReportController.cs | 6 +- .../Startup/ScheduledJobsConfiguration.cs | 2 +- .../Startup/ServiceCollectionExtensions.cs | 67 +- DysonNetwork.Sphere/Sticker/Sticker.cs | 4 +- .../Sticker/StickerController.cs | 54 +- DysonNetwork.Sphere/Sticker/StickerService.cs | 19 +- DysonNetwork.Sphere/Storage/CacheService.cs | 396 -- DysonNetwork.Sphere/Storage/CloudFile.cs | 130 - .../Storage/CloudFileUnusedRecyclingJob.cs | 93 - DysonNetwork.Sphere/Storage/FileController.cs | 154 - .../Storage/FileExpirationJob.cs | 66 - .../Storage/FileReferenceService.cs | 433 -- .../Storage/FileService.ReferenceMigration.cs | 339 -- DysonNetwork.Sphere/Storage/FileService.cs | 555 --- .../Storage/FlushBufferService.cs | 66 - .../Storage/Handlers/ActionLogFlushHandler.cs | 24 - .../Handlers/LastActiveFlushHandler.cs | 60 - .../MessageReadReceiptFlushHandler.cs | 33 - .../Storage/Handlers/PostViewFlushHandler.cs | 52 - DysonNetwork.Sphere/Storage/ICloudFile.cs | 55 - DysonNetwork.Sphere/Storage/TextSanitizer.cs | 33 - DysonNetwork.Sphere/Storage/TusService.cs | 78 - DysonNetwork.Sphere/Wallet/OrderController.cs | 57 - DysonNetwork.Sphere/Wallet/Payment.cs | 63 - .../PaymentHandlers/AfdianPaymentHandler.cs | 446 -- .../PaymentHandlers/ISubscriptionOrder.cs | 18 - DysonNetwork.Sphere/Wallet/PaymentService.cs | 297 -- DysonNetwork.Sphere/Wallet/Subscription.cs | 256 -- .../Wallet/SubscriptionController.cs | 204 - .../Wallet/SubscriptionRenewalJob.cs | 197 - .../Wallet/SubscriptionService.cs | 401 -- DysonNetwork.Sphere/Wallet/Wallet.cs | 24 - .../Wallet/WalletController.cs | 101 - DysonNetwork.Sphere/Wallet/WalletService.cs | 49 - .../WebReader/EmbeddableBase.cs | 2 +- .../{Connection => }/WebReader/LinkEmbed.cs | 2 +- .../WebReader/ScrapedArticle.cs | 2 +- .../{Connection => }/WebReader/WebArticle.cs | 2 +- .../WebReader/WebArticleController.cs | 2 +- .../WebReader/WebFeedController.cs | 23 +- .../WebReader/WebFeedScraperJob.cs | 2 +- .../WebReader/WebFeedService.cs | 2 +- .../WebReader/WebReaderController.cs | 2 +- .../WebReader/WebReaderException.cs | 2 +- .../WebReader/WebReaderService.cs | 8 +- DysonNetwork.sln.DotSettings.user | 1 + 232 files changed, 990 insertions(+), 115807 deletions(-) rename {DysonNetwork.Sphere => DysonNetwork.Pass}/Pages/Checkpoint/CheckpointPage.cshtml (97%) rename {DysonNetwork.Sphere => DysonNetwork.Pass}/Pages/Checkpoint/CheckpointPage.cshtml.cs (86%) create mode 100644 DysonNetwork.Pass/Pages/Shared/_Layout.cshtml rename {DysonNetwork.Sphere => DysonNetwork.Pass}/Pages/Spell/MagicSpellPage.cshtml (97%) rename {DysonNetwork.Sphere => DysonNetwork.Pass}/Pages/Spell/MagicSpellPage.cshtml.cs (95%) create mode 100644 DysonNetwork.Pass/Pages/_ViewImports.cshtml create mode 100644 DysonNetwork.Pass/Pages/_ViewStart.cshtml create mode 100644 DysonNetwork.Shared/Content/TextSanitizer.cs create mode 100644 DysonNetwork.Shared/CultureService.cs rename {DysonNetwork.Pass/Account => DysonNetwork.Shared/Data}/VerificationMark.cs (97%) delete mode 100644 DysonNetwork.Sphere/Account/AbuseReport.cs delete mode 100644 DysonNetwork.Sphere/Account/Account.cs delete mode 100644 DysonNetwork.Sphere/Account/AccountController.cs delete mode 100644 DysonNetwork.Sphere/Account/AccountCurrentController.cs delete mode 100644 DysonNetwork.Sphere/Account/AccountEventService.cs delete mode 100644 DysonNetwork.Sphere/Account/AccountService.cs delete mode 100644 DysonNetwork.Sphere/Account/AccountUsernameService.cs delete mode 100644 DysonNetwork.Sphere/Account/ActionLog.cs delete mode 100644 DysonNetwork.Sphere/Account/ActionLogService.cs delete mode 100644 DysonNetwork.Sphere/Account/Badge.cs delete mode 100644 DysonNetwork.Sphere/Account/Event.cs delete mode 100644 DysonNetwork.Sphere/Account/MagicSpell.cs delete mode 100644 DysonNetwork.Sphere/Account/MagicSpellController.cs delete mode 100644 DysonNetwork.Sphere/Account/MagicSpellService.cs delete mode 100644 DysonNetwork.Sphere/Account/Notification.cs delete mode 100644 DysonNetwork.Sphere/Account/NotificationController.cs delete mode 100644 DysonNetwork.Sphere/Account/NotificationService.cs delete mode 100644 DysonNetwork.Sphere/Account/Relationship.cs delete mode 100644 DysonNetwork.Sphere/Account/RelationshipController.cs delete mode 100644 DysonNetwork.Sphere/Account/RelationshipService.cs delete mode 100644 DysonNetwork.Sphere/Account/VerificationMark.cs delete mode 100644 DysonNetwork.Sphere/Auth/Auth.cs delete mode 100644 DysonNetwork.Sphere/Auth/AuthController.cs delete mode 100644 DysonNetwork.Sphere/Auth/AuthService.cs delete mode 100644 DysonNetwork.Sphere/Auth/CheckpointModel.cs delete mode 100644 DysonNetwork.Sphere/Auth/CompactTokenService.cs delete mode 100644 DysonNetwork.Sphere/Auth/OidcProvider/Controllers/OidcProviderController.cs delete mode 100644 DysonNetwork.Sphere/Auth/OidcProvider/Models/AuthorizationCodeInfo.cs delete mode 100644 DysonNetwork.Sphere/Auth/OidcProvider/Options/OidcProviderOptions.cs delete mode 100644 DysonNetwork.Sphere/Auth/OidcProvider/Responses/AuthorizationResponse.cs delete mode 100644 DysonNetwork.Sphere/Auth/OidcProvider/Responses/ErrorResponse.cs delete mode 100644 DysonNetwork.Sphere/Auth/OidcProvider/Responses/TokenResponse.cs delete mode 100644 DysonNetwork.Sphere/Auth/OidcProvider/Services/OidcProviderService.cs delete mode 100644 DysonNetwork.Sphere/Auth/OpenId/AfdianOidcService.cs delete mode 100644 DysonNetwork.Sphere/Auth/OpenId/AppleMobileSignInRequest.cs delete mode 100644 DysonNetwork.Sphere/Auth/OpenId/AppleOidcService.cs delete mode 100644 DysonNetwork.Sphere/Auth/OpenId/ConnectionController.cs delete mode 100644 DysonNetwork.Sphere/Auth/OpenId/DiscordOidcService.cs delete mode 100644 DysonNetwork.Sphere/Auth/OpenId/GitHubOidcService.cs delete mode 100644 DysonNetwork.Sphere/Auth/OpenId/GoogleOidcService.cs delete mode 100644 DysonNetwork.Sphere/Auth/OpenId/MicrosoftOidcService.cs delete mode 100644 DysonNetwork.Sphere/Auth/OpenId/OidcController.cs delete mode 100644 DysonNetwork.Sphere/Auth/OpenId/OidcService.cs delete mode 100644 DysonNetwork.Sphere/Auth/OpenId/OidcState.cs delete mode 100644 DysonNetwork.Sphere/Auth/OpenId/OidcUserInfo.cs delete mode 100644 DysonNetwork.Sphere/Auth/Session.cs delete mode 100644 DysonNetwork.Sphere/Connection/AutoCompletionController.cs delete mode 100644 DysonNetwork.Sphere/Connection/ClientTypeMiddleware.cs delete mode 100644 DysonNetwork.Sphere/Connection/GeoIpService.cs delete mode 100644 DysonNetwork.Sphere/Connection/Handlers/MessageReadHandler.cs delete mode 100644 DysonNetwork.Sphere/Connection/Handlers/MessageTypingHandler.cs delete mode 100644 DysonNetwork.Sphere/Connection/Handlers/MessagesSubscribeHandler.cs delete mode 100644 DysonNetwork.Sphere/Connection/Handlers/MessagesUnsubscribeHandler.cs delete mode 100644 DysonNetwork.Sphere/Connection/IWebSocketPacketHandler.cs delete mode 100644 DysonNetwork.Sphere/Connection/WebSocketController.cs delete mode 100644 DysonNetwork.Sphere/Connection/WebSocketPacket.cs delete mode 100644 DysonNetwork.Sphere/Connection/WebSocketService.cs delete mode 100644 DysonNetwork.Sphere/Data/Migrations/20250628172328_AddOidcProviderSupport.Designer.cs delete mode 100644 DysonNetwork.Sphere/Data/Migrations/20250628172328_AddOidcProviderSupport.cs delete mode 100644 DysonNetwork.Sphere/Data/Migrations/20250629084150_AuthSessionWithApp.Designer.cs delete mode 100644 DysonNetwork.Sphere/Data/Migrations/20250629084150_AuthSessionWithApp.cs delete mode 100644 DysonNetwork.Sphere/Data/Migrations/20250629123136_CustomAppsRefine.Designer.cs delete mode 100644 DysonNetwork.Sphere/Data/Migrations/20250629123136_CustomAppsRefine.cs delete mode 100644 DysonNetwork.Sphere/Email/EmailModels.cs delete mode 100644 DysonNetwork.Sphere/Email/EmailService.cs delete mode 100644 DysonNetwork.Sphere/Email/RazorViewRenderer.cs delete mode 100644 DysonNetwork.Sphere/Migrations/20250520160525_InitialMigration.Designer.cs delete mode 100644 DysonNetwork.Sphere/Migrations/20250520160525_InitialMigration.cs delete mode 100644 DysonNetwork.Sphere/Migrations/20250521142845_EnrichAccountProfile.Designer.cs delete mode 100644 DysonNetwork.Sphere/Migrations/20250521142845_EnrichAccountProfile.cs delete mode 100644 DysonNetwork.Sphere/Migrations/20250521181143_FixProfileRelationship.Designer.cs delete mode 100644 DysonNetwork.Sphere/Migrations/20250521181143_FixProfileRelationship.cs delete mode 100644 DysonNetwork.Sphere/Migrations/20250523172951_RefactorChatLastRead.Designer.cs delete mode 100644 DysonNetwork.Sphere/Migrations/20250523172951_RefactorChatLastRead.cs delete mode 100644 DysonNetwork.Sphere/Migrations/20250524215045_UpdateRealtimeChat.Designer.cs delete mode 100644 DysonNetwork.Sphere/Migrations/20250524215045_UpdateRealtimeChat.cs delete mode 100644 DysonNetwork.Sphere/Migrations/20250525083412_ModifyRelationshipStatusType.Designer.cs delete mode 100644 DysonNetwork.Sphere/Migrations/20250525083412_ModifyRelationshipStatusType.cs delete mode 100644 DysonNetwork.Sphere/Migrations/20250527144902_LimitedSizeForPictureIdOnPub.Designer.cs delete mode 100644 DysonNetwork.Sphere/Migrations/20250527144902_LimitedSizeForPictureIdOnPub.cs delete mode 100644 DysonNetwork.Sphere/Migrations/20250528171935_AddCloudFileUsage.Designer.cs delete mode 100644 DysonNetwork.Sphere/Migrations/20250528171935_AddCloudFileUsage.cs delete mode 100644 DysonNetwork.Sphere/Migrations/20250601142048_RefactorCloudFileReference.Designer.cs delete mode 100644 DysonNetwork.Sphere/Migrations/20250601142048_RefactorCloudFileReference.cs delete mode 100644 DysonNetwork.Sphere/Migrations/20250602144445_FixPushNotificationIndex.Designer.cs delete mode 100644 DysonNetwork.Sphere/Migrations/20250602144445_FixPushNotificationIndex.cs delete mode 100644 DysonNetwork.Sphere/Migrations/20250603153937_BetterAuthFactor.Designer.cs delete mode 100644 DysonNetwork.Sphere/Migrations/20250603153937_BetterAuthFactor.cs delete mode 100644 DysonNetwork.Sphere/Migrations/20250608114100_AccountContactCanBePrimary.Designer.cs delete mode 100644 DysonNetwork.Sphere/Migrations/20250608114100_AccountContactCanBePrimary.cs delete mode 100644 DysonNetwork.Sphere/Migrations/20250608152205_RemoveActivities.Designer.cs delete mode 100644 DysonNetwork.Sphere/Migrations/20250608152205_RemoveActivities.cs delete mode 100644 DysonNetwork.Sphere/Migrations/20250609153232_EnrichChatMembers.Designer.cs delete mode 100644 DysonNetwork.Sphere/Migrations/20250609153232_EnrichChatMembers.cs delete mode 100644 DysonNetwork.Sphere/Migrations/20250610151739_ActiveBadgeAndVerification.Designer.cs delete mode 100644 DysonNetwork.Sphere/Migrations/20250610151739_ActiveBadgeAndVerification.cs delete mode 100644 DysonNetwork.Sphere/Migrations/20250611165902_BetterRecyclingFilesAndWalletSubscriptions.Designer.cs delete mode 100644 DysonNetwork.Sphere/Migrations/20250611165902_BetterRecyclingFilesAndWalletSubscriptions.cs delete mode 100644 DysonNetwork.Sphere/Migrations/20250615083256_AddAccountConnection.Designer.cs delete mode 100644 DysonNetwork.Sphere/Migrations/20250615083256_AddAccountConnection.cs delete mode 100644 DysonNetwork.Sphere/Migrations/20250615154147_EnrichAccountConnection.Designer.cs delete mode 100644 DysonNetwork.Sphere/Migrations/20250615154147_EnrichAccountConnection.cs delete mode 100644 DysonNetwork.Sphere/Migrations/20250621191505_WalletOrderAppDX.Designer.cs delete mode 100644 DysonNetwork.Sphere/Migrations/20250621191505_WalletOrderAppDX.cs delete mode 100644 DysonNetwork.Sphere/Migrations/20250624160304_DropActionLogSessionFk.Designer.cs delete mode 100644 DysonNetwork.Sphere/Migrations/20250624160304_DropActionLogSessionFk.cs delete mode 100644 DysonNetwork.Sphere/Migrations/20250625150644_SafetyAbuseReport.Designer.cs delete mode 100644 DysonNetwork.Sphere/Migrations/20250625150644_SafetyAbuseReport.cs delete mode 100644 DysonNetwork.Sphere/Migrations/20250626093051_AddWebArticles.Designer.cs delete mode 100644 DysonNetwork.Sphere/Migrations/20250626093051_AddWebArticles.cs delete mode 100644 DysonNetwork.Sphere/Migrations/20250626105203_AddRealmTags.Designer.cs delete mode 100644 DysonNetwork.Sphere/Migrations/20250626105203_AddRealmTags.cs delete mode 100644 DysonNetwork.Sphere/Migrations/AppDatabaseModelSnapshot.cs delete mode 100644 DysonNetwork.Sphere/Pages/Account/Profile.cshtml delete mode 100644 DysonNetwork.Sphere/Pages/Account/Profile.cshtml.cs delete mode 100644 DysonNetwork.Sphere/Pages/Auth/Authorize.cshtml delete mode 100644 DysonNetwork.Sphere/Pages/Auth/Authorize.cshtml.cs delete mode 100644 DysonNetwork.Sphere/Pages/Auth/Callback.cshtml delete mode 100644 DysonNetwork.Sphere/Pages/Auth/Callback.cshtml.cs delete mode 100644 DysonNetwork.Sphere/Pages/Auth/Challenge.cshtml delete mode 100644 DysonNetwork.Sphere/Pages/Auth/Challenge.cshtml.cs delete mode 100644 DysonNetwork.Sphere/Pages/Auth/Login.cshtml delete mode 100644 DysonNetwork.Sphere/Pages/Auth/Login.cshtml.cs delete mode 100644 DysonNetwork.Sphere/Pages/Auth/SelectFactor.cshtml delete mode 100644 DysonNetwork.Sphere/Pages/Auth/SelectFactor.cshtml.cs delete mode 100644 DysonNetwork.Sphere/Pages/Auth/VerifyFactor.cshtml delete mode 100644 DysonNetwork.Sphere/Pages/Auth/VerifyFactor.cshtml.cs delete mode 100644 DysonNetwork.Sphere/Storage/CacheService.cs delete mode 100644 DysonNetwork.Sphere/Storage/CloudFile.cs delete mode 100644 DysonNetwork.Sphere/Storage/CloudFileUnusedRecyclingJob.cs delete mode 100644 DysonNetwork.Sphere/Storage/FileController.cs delete mode 100644 DysonNetwork.Sphere/Storage/FileExpirationJob.cs delete mode 100644 DysonNetwork.Sphere/Storage/FileReferenceService.cs delete mode 100644 DysonNetwork.Sphere/Storage/FileService.ReferenceMigration.cs delete mode 100644 DysonNetwork.Sphere/Storage/FileService.cs delete mode 100644 DysonNetwork.Sphere/Storage/FlushBufferService.cs delete mode 100644 DysonNetwork.Sphere/Storage/Handlers/ActionLogFlushHandler.cs delete mode 100644 DysonNetwork.Sphere/Storage/Handlers/LastActiveFlushHandler.cs delete mode 100644 DysonNetwork.Sphere/Storage/Handlers/MessageReadReceiptFlushHandler.cs delete mode 100644 DysonNetwork.Sphere/Storage/Handlers/PostViewFlushHandler.cs delete mode 100644 DysonNetwork.Sphere/Storage/ICloudFile.cs delete mode 100644 DysonNetwork.Sphere/Storage/TextSanitizer.cs delete mode 100644 DysonNetwork.Sphere/Storage/TusService.cs delete mode 100644 DysonNetwork.Sphere/Wallet/OrderController.cs delete mode 100644 DysonNetwork.Sphere/Wallet/Payment.cs delete mode 100644 DysonNetwork.Sphere/Wallet/PaymentHandlers/AfdianPaymentHandler.cs delete mode 100644 DysonNetwork.Sphere/Wallet/PaymentHandlers/ISubscriptionOrder.cs delete mode 100644 DysonNetwork.Sphere/Wallet/PaymentService.cs delete mode 100644 DysonNetwork.Sphere/Wallet/Subscription.cs delete mode 100644 DysonNetwork.Sphere/Wallet/SubscriptionController.cs delete mode 100644 DysonNetwork.Sphere/Wallet/SubscriptionRenewalJob.cs delete mode 100644 DysonNetwork.Sphere/Wallet/SubscriptionService.cs delete mode 100644 DysonNetwork.Sphere/Wallet/Wallet.cs delete mode 100644 DysonNetwork.Sphere/Wallet/WalletController.cs delete mode 100644 DysonNetwork.Sphere/Wallet/WalletService.cs rename DysonNetwork.Sphere/{Connection => }/WebReader/EmbeddableBase.cs (95%) rename DysonNetwork.Sphere/{Connection => }/WebReader/LinkEmbed.cs (96%) rename DysonNetwork.Sphere/{Connection => }/WebReader/ScrapedArticle.cs (70%) rename DysonNetwork.Sphere/{Connection => }/WebReader/WebArticle.cs (96%) rename DysonNetwork.Sphere/{Connection => }/WebReader/WebArticleController.cs (97%) rename DysonNetwork.Sphere/{Connection => }/WebReader/WebFeedController.cs (78%) rename DysonNetwork.Sphere/{Connection => }/WebReader/WebFeedScraperJob.cs (94%) rename DysonNetwork.Sphere/{Connection => }/WebReader/WebFeedService.cs (98%) rename DysonNetwork.Sphere/{Connection => }/WebReader/WebReaderController.cs (98%) rename DysonNetwork.Sphere/{Connection => }/WebReader/WebReaderException.cs (87%) rename DysonNetwork.Sphere/{Connection => }/WebReader/WebReaderService.cs (99%) diff --git a/DysonNetwork.Drive/Migrations/20250713121317_InitialMigration.Designer.cs b/DysonNetwork.Drive/Migrations/20250713121317_InitialMigration.Designer.cs index cdcbf47..6a7a144 100644 --- a/DysonNetwork.Drive/Migrations/20250713121317_InitialMigration.Designer.cs +++ b/DysonNetwork.Drive/Migrations/20250713121317_InitialMigration.Designer.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using DysonNetwork.Drive; using DysonNetwork.Drive.Storage; +using DysonNetwork.Shared.Data; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Migrations; diff --git a/DysonNetwork.Drive/Migrations/20250713121317_InitialMigration.cs b/DysonNetwork.Drive/Migrations/20250713121317_InitialMigration.cs index 48977cd..54aa9f2 100644 --- a/DysonNetwork.Drive/Migrations/20250713121317_InitialMigration.cs +++ b/DysonNetwork.Drive/Migrations/20250713121317_InitialMigration.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using DysonNetwork.Drive.Storage; +using DysonNetwork.Shared.Data; using Microsoft.EntityFrameworkCore.Migrations; using NodaTime; diff --git a/DysonNetwork.Drive/Migrations/AppDatabaseModelSnapshot.cs b/DysonNetwork.Drive/Migrations/AppDatabaseModelSnapshot.cs index e8d6e77..62b7a87 100644 --- a/DysonNetwork.Drive/Migrations/AppDatabaseModelSnapshot.cs +++ b/DysonNetwork.Drive/Migrations/AppDatabaseModelSnapshot.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using DysonNetwork.Drive; using DysonNetwork.Drive.Storage; +using DysonNetwork.Shared.Data; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; diff --git a/DysonNetwork.Drive/Storage/CloudFile.cs b/DysonNetwork.Drive/Storage/CloudFile.cs index 81d9239..8ec116d 100644 --- a/DysonNetwork.Drive/Storage/CloudFile.cs +++ b/DysonNetwork.Drive/Storage/CloudFile.cs @@ -95,7 +95,7 @@ public class CloudFile : ModelBase, ICloudFile, IIdentifiedResource }; } - public string ResourceIdentifier => $"file/{Id}"; + public string ResourceIdentifier => $"file:{Id}"; /// /// Converts the CloudFile to a protobuf message @@ -138,23 +138,6 @@ public class CloudFile : ModelBase, ICloudFile, IIdentifiedResource } } -public enum ContentSensitiveMark -{ - Language, - SexualContent, - Violence, - Profanity, - HateSpeech, - Racism, - AdultContent, - DrugAbuse, - AlcoholAbuse, - Gambling, - SelfHarm, - ChildAbuse, - Other -} - public class CloudFileReference : ModelBase { public Guid Id { get; set; } = Guid.NewGuid(); diff --git a/DysonNetwork.Drive/Storage/FileReferenceService.cs b/DysonNetwork.Drive/Storage/FileReferenceService.cs index 94a704a..24d1de3 100644 --- a/DysonNetwork.Drive/Storage/FileReferenceService.cs +++ b/DysonNetwork.Drive/Storage/FileReferenceService.cs @@ -1,4 +1,5 @@ using DysonNetwork.Shared.Cache; +using EFCore.BulkExtensions; using Microsoft.EntityFrameworkCore; using NodaTime; @@ -19,11 +20,12 @@ public class FileReferenceService(AppDatabase db, FileService fileService, ICach /// Optional duration after which the file expires (alternative to expiredAt) /// The created file reference public async Task CreateReferenceAsync( - string fileId, - string usage, - string resourceId, - Instant? expiredAt = null, - Duration? duration = null) + string fileId, + string usage, + string resourceId, + Instant? expiredAt = null, + Duration? duration = null + ) { // Calculate expiration time if needed var finalExpiration = expiredAt; @@ -46,6 +48,25 @@ public class FileReferenceService(AppDatabase db, FileService fileService, ICach return reference; } + public async Task> CreateReferencesAsync( + List fileId, + string usage, + string resourceId, + Instant? expiredAt = null, + Duration? duration = null + ) + { + var data = fileId.Select(id => new CloudFileReference + { + FileId = id, + Usage = usage, + ResourceId = resourceId, + ExpiredAt = expiredAt ?? SystemClock.Instance.GetCurrentInstant() + duration + }).ToList(); + await db.BulkInsertAsync(data); + return data; + } + /// /// Gets all references to a file /// @@ -274,8 +295,8 @@ public class FileReferenceService(AppDatabase db, FileService fileService, ICach // Update newly added references with the expiration time var referenceIds = await db.FileReferences - .Where(r => toAdd.Select(a => a.FileId).Contains(r.FileId) && - r.ResourceId == resourceId && + .Where(r => toAdd.Select(a => a.FileId).Contains(r.FileId) && + r.ResourceId == resourceId && r.Usage == usage) .Select(r => r.Id) .ToListAsync(); @@ -431,4 +452,4 @@ public class FileReferenceService(AppDatabase db, FileService fileService, ICach return await SetReferenceExpirationAsync(referenceId, expiredAt); } -} +} \ No newline at end of file diff --git a/DysonNetwork.Drive/Storage/FileReferenceServiceGrpc.cs b/DysonNetwork.Drive/Storage/FileReferenceServiceGrpc.cs index 2a1459e..386b982 100644 --- a/DysonNetwork.Drive/Storage/FileReferenceServiceGrpc.cs +++ b/DysonNetwork.Drive/Storage/FileReferenceServiceGrpc.cs @@ -27,6 +27,27 @@ namespace DysonNetwork.Drive.Storage return reference.ToProtoValue(); } + public override async Task CreateReferenceBatch(CreateReferenceBatchRequest request, + ServerCallContext context) + { + Instant? expiredAt = null; + if (request.ExpiredAt != null) + expiredAt = Instant.FromUnixTimeSeconds(request.ExpiredAt.Seconds); + else if (request.Duration != null) + expiredAt = SystemClock.Instance.GetCurrentInstant() + + Duration.FromTimeSpan(request.Duration.ToTimeSpan()); + + var references = await fileReferenceService.CreateReferencesAsync( + request.FilesId.ToList(), + request.Usage, + request.ResourceId, + expiredAt + ); + var response = new CreateReferenceBatchResponse(); + response.References.AddRange(references.Select(r => r.ToProtoValue())); + return response; + } + public override async Task GetReferences(GetReferencesRequest request, ServerCallContext context) { diff --git a/DysonNetwork.Drive/Storage/FileService.cs b/DysonNetwork.Drive/Storage/FileService.cs index d18868d..6131a18 100644 --- a/DysonNetwork.Drive/Storage/FileService.cs +++ b/DysonNetwork.Drive/Storage/FileService.cs @@ -50,6 +50,51 @@ public class FileService( return file; } + + public async Task> GetFilesAsync(List fileIds) + { + var cachedFiles = new Dictionary(); + var uncachedIds = new List(); + + // Check cache first + foreach (var fileId in fileIds) + { + var cacheKey = $"{CacheKeyPrefix}{fileId}"; + var cachedFile = await cache.GetAsync(cacheKey); + + if (cachedFile != null) + { + cachedFiles[fileId] = cachedFile; + } + else + { + uncachedIds.Add(fileId); + } + } + + // Load uncached files from database + if (uncachedIds.Count > 0) + { + var dbFiles = await db.Files + .Where(f => uncachedIds.Contains(f.Id)) + .ToListAsync(); + + // Add to cache + foreach (var file in dbFiles) + { + var cacheKey = $"{CacheKeyPrefix}{file.Id}"; + await cache.SetAsync(cacheKey, file, CacheDuration); + cachedFiles[file.Id] = file; + } + } + + // Preserve original order + return fileIds + .Select(f => cachedFiles.GetValueOrDefault(f)) + .Where(f => f != null) + .Cast() + .ToList(); + } private static readonly string TempFilePrefix = "dyn-cloudfile"; @@ -155,7 +200,7 @@ public class FileService( try { var mediaInfo = await FFProbe.AnalyseAsync(ogFilePath); - file.FileMeta = new Dictionary + file.FileMeta = new Dictionary { ["duration"] = mediaInfo.Duration.TotalSeconds, ["format_name"] = mediaInfo.Format.FormatName, diff --git a/DysonNetwork.Drive/Storage/FileServiceGrpc.cs b/DysonNetwork.Drive/Storage/FileServiceGrpc.cs index 4b62afb..ba4c212 100644 --- a/DysonNetwork.Drive/Storage/FileServiceGrpc.cs +++ b/DysonNetwork.Drive/Storage/FileServiceGrpc.cs @@ -12,6 +12,12 @@ namespace DysonNetwork.Drive.Storage return file?.ToProtoValue() ?? throw new RpcException(new Status(StatusCode.NotFound, "File not found")); } + public override async Task GetFileBatch(GetFileBatchRequest request, ServerCallContext context) + { + var files = await fileService.GetFilesAsync(request.Ids.ToList()); + return new GetFileBatchResponse { Files = { files.Select(f => f.ToProtoValue()) } }; + } + public override async Task UpdateFile(UpdateFileRequest request, ServerCallContext context) { diff --git a/DysonNetwork.Pass/Account/AccountServiceGrpc.cs b/DysonNetwork.Pass/Account/AccountServiceGrpc.cs index d79d8ac..c94ae1d 100644 --- a/DysonNetwork.Pass/Account/AccountServiceGrpc.cs +++ b/DysonNetwork.Pass/Account/AccountServiceGrpc.cs @@ -9,6 +9,7 @@ namespace DysonNetwork.Pass.Account; public class AccountServiceGrpc( AppDatabase db, + RelationshipService relationships, IClock clock, ILogger logger ) @@ -19,7 +20,7 @@ public class AccountServiceGrpc( private readonly ILogger _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - + public override async Task GetAccount(GetAccountRequest request, ServerCallContext context) { if (!Guid.TryParse(request.Id, out var accountId)) @@ -36,7 +37,8 @@ public class AccountServiceGrpc( return account.ToProtoValue(); } - public override async Task GetAccountBatch(GetAccountBatchRequest request, ServerCallContext context) + public override async Task GetAccountBatch(GetAccountBatchRequest request, + ServerCallContext context) { var accountIds = request.Id .Select(id => Guid.TryParse(id, out var accountId) ? accountId : (Guid?)null) @@ -245,7 +247,8 @@ public class AccountServiceGrpc( return new Empty(); } - public override async Task ListContacts(ListContactsRequest request, ServerCallContext context) + public override async Task ListContacts(ListContactsRequest request, + ServerCallContext context) { if (!Guid.TryParse(request.AccountId, out var accountId)) throw new RpcException(new Grpc.Core.Status(StatusCode.InvalidArgument, "Invalid account ID format")); @@ -263,7 +266,8 @@ public class AccountServiceGrpc( return response; } - public override async Task VerifyContact(VerifyContactRequest request, ServerCallContext context) + public override async Task VerifyContact(VerifyContactRequest request, + ServerCallContext context) { // This is a placeholder implementation. In a real-world scenario, you would // have a more robust verification mechanism (e.g., sending a code to the @@ -343,7 +347,8 @@ public class AccountServiceGrpc( return response; } - public override async Task SetActiveBadge(SetActiveBadgeRequest request, ServerCallContext context) + public override async Task SetActiveBadge(SetActiveBadgeRequest request, + ServerCallContext context) { if (!Guid.TryParse(request.AccountId, out var accountId)) throw new RpcException(new Grpc.Core.Status(StatusCode.InvalidArgument, "Invalid account ID format")); @@ -359,4 +364,55 @@ public class AccountServiceGrpc( return profile.ToProtoValue(); } + + public override async Task ListFriends( + ListUserRelationshipSimpleRequest request, ServerCallContext context) + { + var accountId = Guid.Parse(request.AccountId); + var relationship = await relationships.ListAccountFriends(accountId); + var resp = new ListUserRelationshipSimpleResponse(); + resp.AccountsId.AddRange(relationship.Select(x => x.ToString())); + return resp; + } + + public override async Task ListBlocked( + ListUserRelationshipSimpleRequest request, ServerCallContext context) + { + var accountId = Guid.Parse(request.AccountId); + var relationship = await relationships.ListAccountBlocked(accountId); + var resp = new ListUserRelationshipSimpleResponse(); + resp.AccountsId.AddRange(relationship.Select(x => x.ToString())); + return resp; + } + + public override async Task GetRelationship(GetRelationshipRequest request, + ServerCallContext context) + { + var relationship = await relationships.GetRelationship( + Guid.Parse(request.AccountId), + Guid.Parse(request.RelatedId), + status: (RelationshipStatus?)request.Status + ); + return new GetRelationshipResponse + { + Relationship = relationship?.ToProtoValue() + }; + } + + public override async Task HasRelationship(GetRelationshipRequest request, ServerCallContext context) + { + var hasRelationship = false; + if (!request.HasStatus) + hasRelationship = await relationships.HasExistingRelationship( + Guid.Parse(request.AccountId), + Guid.Parse(request.RelatedId) + ); + else + hasRelationship = await relationships.HasRelationshipWithStatus( + Guid.Parse(request.AccountId), + Guid.Parse(request.RelatedId), + (RelationshipStatus)request.Status + ); + return new BoolValue { Value = hasRelationship }; + } } \ No newline at end of file diff --git a/DysonNetwork.Pass/Account/Relationship.cs b/DysonNetwork.Pass/Account/Relationship.cs index 9131b78..0b169b7 100644 --- a/DysonNetwork.Pass/Account/Relationship.cs +++ b/DysonNetwork.Pass/Account/Relationship.cs @@ -1,5 +1,7 @@ using DysonNetwork.Shared.Data; +using DysonNetwork.Shared.Proto; using NodaTime; +using NodaTime.Serialization.Protobuf; namespace DysonNetwork.Pass.Account; @@ -20,4 +22,15 @@ public class Relationship : ModelBase public Instant? ExpiredAt { get; set; } public RelationshipStatus Status { get; set; } = RelationshipStatus.Pending; + + public Shared.Proto.Relationship ToProtoValue() => new() + { + AccountId = AccountId.ToString(), + RelatedId = RelatedId.ToString(), + Account = Account.ToProtoValue(), + Related = Related.ToProtoValue(), + Type = (int)Status, + CreatedAt = CreatedAt.ToTimestamp(), + UpdatedAt = UpdatedAt.ToTimestamp() + }; } \ No newline at end of file diff --git a/DysonNetwork.Pass/Account/RelationshipService.cs b/DysonNetwork.Pass/Account/RelationshipService.cs index 20be861..0b2bc03 100644 --- a/DysonNetwork.Pass/Account/RelationshipService.cs +++ b/DysonNetwork.Pass/Account/RelationshipService.cs @@ -154,13 +154,18 @@ public class RelationshipService(AppDatabase db, ICacheService cache) public async Task> ListAccountFriends(Account account) { - var cacheKey = $"{UserFriendsCacheKeyPrefix}{account.Id}"; + return await ListAccountFriends(account.Id); + } + + public async Task> ListAccountFriends(Guid accountId) + { + var cacheKey = $"{UserFriendsCacheKeyPrefix}{accountId}"; var friends = await cache.GetAsync>(cacheKey); if (friends == null) { friends = await db.AccountRelationships - .Where(r => r.RelatedId == account.Id) + .Where(r => r.RelatedId == accountId) .Where(r => r.Status == RelationshipStatus.Friends) .Select(r => r.AccountId) .ToListAsync(); @@ -173,13 +178,18 @@ public class RelationshipService(AppDatabase db, ICacheService cache) public async Task> ListAccountBlocked(Account account) { - var cacheKey = $"{UserBlockedCacheKeyPrefix}{account.Id}"; + return await ListAccountBlocked(account.Id); + } + + public async Task> ListAccountBlocked(Guid accountId) + { + var cacheKey = $"{UserBlockedCacheKeyPrefix}{accountId}"; var blocked = await cache.GetAsync>(cacheKey); if (blocked == null) { blocked = await db.AccountRelationships - .Where(r => r.RelatedId == account.Id) + .Where(r => r.RelatedId == accountId) .Where(r => r.Status == RelationshipStatus.Blocked) .Select(r => r.AccountId) .ToListAsync(); diff --git a/DysonNetwork.Pass/Auth/AuthServiceGrpc.cs b/DysonNetwork.Pass/Auth/AuthServiceGrpc.cs index 996dab4..b3e956c 100644 --- a/DysonNetwork.Pass/Auth/AuthServiceGrpc.cs +++ b/DysonNetwork.Pass/Auth/AuthServiceGrpc.cs @@ -20,7 +20,7 @@ public class AuthServiceGrpc( { if (!authService.ValidateToken(request.Token, out var sessionId)) return new AuthenticateResponse { Valid = false, Message = "Invalid token." }; - + var session = await cache.GetAsync($"{DysonTokenAuthHandler.AuthCachePrefix}{sessionId}"); if (session is not null) return new AuthenticateResponse { Valid = true, Session = session.ToProtoValue() }; @@ -36,7 +36,7 @@ public class AuthServiceGrpc( var now = SystemClock.Instance.GetCurrentInstant(); if (session.ExpiredAt.HasValue && session.ExpiredAt < now) return new AuthenticateResponse { Valid = false, Message = "Session has been expired." }; - + await cache.SetWithGroupsAsync( $"auth:{sessionId}", session, @@ -46,4 +46,17 @@ public class AuthServiceGrpc( return new AuthenticateResponse { Valid = true, Session = session.ToProtoValue() }; } + + public override async Task ValidatePin(ValidatePinRequest request, ServerCallContext context) + { + var accountId = Guid.Parse(request.AccountId); + var valid = await authService.ValidatePinCode(accountId, request.Pin); + return new ValidateResponse { Valid = valid }; + } + + public override async Task ValidateCaptcha(ValidateCaptchaRequest request, ServerCallContext context) + { + var valid = await authService.ValidateCaptcha(request.Token); + return new ValidateResponse { Valid = valid }; + } } \ No newline at end of file diff --git a/DysonNetwork.Pass/Developer/CustomApp.cs b/DysonNetwork.Pass/Developer/CustomApp.cs index 731d8cc..2910dac 100644 --- a/DysonNetwork.Pass/Developer/CustomApp.cs +++ b/DysonNetwork.Pass/Developer/CustomApp.cs @@ -34,7 +34,7 @@ public class CustomApp : ModelBase, IIdentifiedResource // TODO: Publisher - [NotMapped] public string ResourceIdentifier => "custom-app/" + Id; + [NotMapped] public string ResourceIdentifier => "custom-app:" + Id; } public class CustomAppLinks diff --git a/DysonNetwork.Pass/DysonNetwork.Pass.csproj b/DysonNetwork.Pass/DysonNetwork.Pass.csproj index b781b58..bf6bab7 100644 --- a/DysonNetwork.Pass/DysonNetwork.Pass.csproj +++ b/DysonNetwork.Pass/DysonNetwork.Pass.csproj @@ -100,12 +100,16 @@ + + + + diff --git a/DysonNetwork.Sphere/Pages/Checkpoint/CheckpointPage.cshtml b/DysonNetwork.Pass/Pages/Checkpoint/CheckpointPage.cshtml similarity index 97% rename from DysonNetwork.Sphere/Pages/Checkpoint/CheckpointPage.cshtml rename to DysonNetwork.Pass/Pages/Checkpoint/CheckpointPage.cshtml index cd392bc..ebb8e27 100644 --- a/DysonNetwork.Sphere/Pages/Checkpoint/CheckpointPage.cshtml +++ b/DysonNetwork.Pass/Pages/Checkpoint/CheckpointPage.cshtml @@ -1,5 +1,5 @@ @page "/auth/captcha" -@model DysonNetwork.Sphere.Pages.Checkpoint.CheckpointPage +@model DysonNetwork.Pass.Pages.Checkpoint.CheckpointPage @{ ViewData["Title"] = "Security Checkpoint"; @@ -99,7 +99,7 @@
Hosted by - DysonNetwork.Sphere + DysonNetwork.Pass diff --git a/DysonNetwork.Sphere/Pages/Checkpoint/CheckpointPage.cshtml.cs b/DysonNetwork.Pass/Pages/Checkpoint/CheckpointPage.cshtml.cs similarity index 86% rename from DysonNetwork.Sphere/Pages/Checkpoint/CheckpointPage.cshtml.cs rename to DysonNetwork.Pass/Pages/Checkpoint/CheckpointPage.cshtml.cs index 69f53e9..ac561ce 100644 --- a/DysonNetwork.Sphere/Pages/Checkpoint/CheckpointPage.cshtml.cs +++ b/DysonNetwork.Pass/Pages/Checkpoint/CheckpointPage.cshtml.cs @@ -1,7 +1,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; -namespace DysonNetwork.Sphere.Pages.Checkpoint; +namespace DysonNetwork.Pass.Pages.Checkpoint; public class CheckpointPage(IConfiguration configuration) : PageModel { diff --git a/DysonNetwork.Pass/Pages/Shared/_Layout.cshtml b/DysonNetwork.Pass/Pages/Shared/_Layout.cshtml new file mode 100644 index 0000000..7f063bf --- /dev/null +++ b/DysonNetwork.Pass/Pages/Shared/_Layout.cshtml @@ -0,0 +1,62 @@ +@using DysonNetwork.Pass.Auth + + + + + + + @ViewData["Title"] + + + + + + + @await RenderSectionAsync("Head", required: false) + + + + +
+ @RenderBody() +
+ +@await RenderSectionAsync("Scripts", required: false) + + \ No newline at end of file diff --git a/DysonNetwork.Sphere/Pages/Spell/MagicSpellPage.cshtml b/DysonNetwork.Pass/Pages/Spell/MagicSpellPage.cshtml similarity index 97% rename from DysonNetwork.Sphere/Pages/Spell/MagicSpellPage.cshtml rename to DysonNetwork.Pass/Pages/Spell/MagicSpellPage.cshtml index 1828549..bd92e93 100644 --- a/DysonNetwork.Sphere/Pages/Spell/MagicSpellPage.cshtml +++ b/DysonNetwork.Pass/Pages/Spell/MagicSpellPage.cshtml @@ -1,6 +1,6 @@ @page "/spells/{spellWord}" -@using DysonNetwork.Sphere.Account -@model DysonNetwork.Sphere.Pages.Spell.MagicSpellPage +@using DysonNetwork.Pass.Account +@model DysonNetwork.Pass.Pages.Spell.MagicSpellPage @{ ViewData["Title"] = "Magic Spell"; @@ -82,7 +82,7 @@
Powered by - DysonNetwork.Sphere + DysonNetwork.Pass diff --git a/DysonNetwork.Sphere/Pages/Spell/MagicSpellPage.cshtml.cs b/DysonNetwork.Pass/Pages/Spell/MagicSpellPage.cshtml.cs similarity index 95% rename from DysonNetwork.Sphere/Pages/Spell/MagicSpellPage.cshtml.cs rename to DysonNetwork.Pass/Pages/Spell/MagicSpellPage.cshtml.cs index 45dc2b7..14d57a9 100644 --- a/DysonNetwork.Sphere/Pages/Spell/MagicSpellPage.cshtml.cs +++ b/DysonNetwork.Pass/Pages/Spell/MagicSpellPage.cshtml.cs @@ -1,10 +1,10 @@ -using DysonNetwork.Sphere.Account; +using DysonNetwork.Pass.Account; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.EntityFrameworkCore; using NodaTime; -namespace DysonNetwork.Sphere.Pages.Spell; +namespace DysonNetwork.Pass.Pages.Spell; public class MagicSpellPage(AppDatabase db, MagicSpellService spells) : PageModel { diff --git a/DysonNetwork.Pass/Pages/_ViewImports.cshtml b/DysonNetwork.Pass/Pages/_ViewImports.cshtml new file mode 100644 index 0000000..4b4ce1d --- /dev/null +++ b/DysonNetwork.Pass/Pages/_ViewImports.cshtml @@ -0,0 +1,2 @@ +@namespace DysonNetwork.Pass.Pages +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers \ No newline at end of file diff --git a/DysonNetwork.Pass/Pages/_ViewStart.cshtml b/DysonNetwork.Pass/Pages/_ViewStart.cshtml new file mode 100644 index 0000000..40c70bc --- /dev/null +++ b/DysonNetwork.Pass/Pages/_ViewStart.cshtml @@ -0,0 +1,3 @@ +@{ + Layout = "_Layout"; +} \ No newline at end of file diff --git a/DysonNetwork.Shared/Content/TextSanitizer.cs b/DysonNetwork.Shared/Content/TextSanitizer.cs new file mode 100644 index 0000000..6092a31 --- /dev/null +++ b/DysonNetwork.Shared/Content/TextSanitizer.cs @@ -0,0 +1,40 @@ +using System.Text; +using System.Globalization; +using System.Text.RegularExpressions; + +namespace DysonNetwork.Shared.Content; + +public abstract partial class TextSanitizer +{ + [GeneratedRegex(@"[\u0000-\u001F\u007F\u200B-\u200F\u202A-\u202E\u2060-\u206F\uFFF0-\uFFFF]")] + private static partial Regex WeirdUnicodeRegex(); + + [GeneratedRegex(@"[\r\n]+")] + private static partial Regex NewlineRegex(); + + public static string? Sanitize(string? text) + { + if (text is null) return null; + + // Normalize weird Unicode characters + var cleaned = WeirdUnicodeRegex().Replace(text, ""); + + // Normalize bold/italic/fancy unicode letters to ASCII + cleaned = NormalizeFancyUnicode(cleaned); + + // Replace multiple newlines with a single newline + cleaned = NewlineRegex().Replace(cleaned, "\n"); + + return cleaned; + } + + private static string NormalizeFancyUnicode(string input) + { + var sb = new StringBuilder(input.Length); + foreach (var c in input.Normalize(NormalizationForm.FormKC).Where(c => + char.GetUnicodeCategory(c) != UnicodeCategory.NonSpacingMark)) + sb.Append(c); + + return sb.ToString(); + } +} \ No newline at end of file diff --git a/DysonNetwork.Shared/CultureService.cs b/DysonNetwork.Shared/CultureService.cs new file mode 100644 index 0000000..2c6db7d --- /dev/null +++ b/DysonNetwork.Shared/CultureService.cs @@ -0,0 +1,19 @@ +using System.Globalization; +using DysonNetwork.Shared.Proto; + +namespace DysonNetwork.Shared; + +public static class CultureService +{ + public static void SetCultureInfo(string? languageCode) + { + var info = new CultureInfo(languageCode ?? "en-us", false); + CultureInfo.CurrentCulture = info; + CultureInfo.CurrentUICulture = info; + } + + public static void SetCultureInfo(Account account) + { + SetCultureInfo(account.Language); + } +} \ No newline at end of file diff --git a/DysonNetwork.Shared/Data/CloudFileReferenceObject.cs b/DysonNetwork.Shared/Data/CloudFileReferenceObject.cs index 4866115..ec69d00 100644 --- a/DysonNetwork.Shared/Data/CloudFileReferenceObject.cs +++ b/DysonNetwork.Shared/Data/CloudFileReferenceObject.cs @@ -3,6 +3,23 @@ using Google.Protobuf.WellKnownTypes; namespace DysonNetwork.Shared.Data; +public enum ContentSensitiveMark +{ + Language, + SexualContent, + Violence, + Profanity, + HateSpeech, + Racism, + AdultContent, + DrugAbuse, + AlcoholAbuse, + Gambling, + SelfHarm, + ChildAbuse, + Other +} + /// /// The class that used in jsonb columns which referenced the cloud file. /// The aim of this class is to store some properties that won't change to a file to reduce the database load. diff --git a/DysonNetwork.Pass/Account/VerificationMark.cs b/DysonNetwork.Shared/Data/VerificationMark.cs similarity index 97% rename from DysonNetwork.Pass/Account/VerificationMark.cs rename to DysonNetwork.Shared/Data/VerificationMark.cs index dbd4159..f22febd 100644 --- a/DysonNetwork.Pass/Account/VerificationMark.cs +++ b/DysonNetwork.Shared/Data/VerificationMark.cs @@ -1,6 +1,6 @@ using System.ComponentModel.DataAnnotations; -namespace DysonNetwork.Pass.Account; +namespace DysonNetwork.Shared.Data; /// /// The verification info of a resource diff --git a/DysonNetwork.Shared/Proto/GrpcClientHelper.cs b/DysonNetwork.Shared/Proto/GrpcClientHelper.cs index 49ce2c6..f254575 100644 --- a/DysonNetwork.Shared/Proto/GrpcClientHelper.cs +++ b/DysonNetwork.Shared/Proto/GrpcClientHelper.cs @@ -73,4 +73,28 @@ public static class GrpcClientHelper return new PusherService.PusherServiceClient(CreateCallInvoker(url, clientCertPath, clientKeyPath, clientCertPassword)); } + + public static async Task CreateFileServiceClient( + IEtcdClient etcdClient, + string clientCertPath, + string clientKeyPath, + string? clientCertPassword = null + ) + { + var url = await GetServiceUrlFromEtcd(etcdClient, "DysonNetwork.File"); + return new FileService.FileServiceClient(CreateCallInvoker(url, clientCertPath, clientKeyPath, + clientCertPassword)); + } + + public static async Task CreateFileReferenceServiceClient( + IEtcdClient etcdClient, + string clientCertPath, + string clientKeyPath, + string? clientCertPassword = null + ) + { + var url = await GetServiceUrlFromEtcd(etcdClient, "DysonNetwork.FileReference"); + return new FileReferenceService.FileReferenceServiceClient(CreateCallInvoker(url, clientCertPath, clientKeyPath, + clientCertPassword)); + } } \ No newline at end of file diff --git a/DysonNetwork.Shared/Proto/account.proto b/DysonNetwork.Shared/Proto/account.proto index dd332e6..c66e304 100644 --- a/DysonNetwork.Shared/Proto/account.proto +++ b/DysonNetwork.Shared/Proto/account.proto @@ -20,7 +20,7 @@ message Account { string language = 4; google.protobuf.Timestamp activated_at = 5; bool is_superuser = 6; - + AccountProfile profile = 7; repeated AccountContact contacts = 8; repeated AccountBadge badges = 9; @@ -43,17 +43,17 @@ message AccountProfile { google.protobuf.StringValue location = 9; google.protobuf.Timestamp birthday = 10; google.protobuf.Timestamp last_seen_at = 11; - + VerificationMark verification = 12; BadgeReferenceObject active_badge = 13; - + int32 experience = 14; int32 level = 15; double leveling_progress = 16; - + CloudFile picture = 19; CloudFile background = 20; - + string account_id = 21; } @@ -155,24 +155,15 @@ message BadgeReferenceObject { // Relationship represents a connection between two accounts message Relationship { - string id = 1; - string from_account_id = 2; - string to_account_id = 3; - RelationshipType type = 4; - string note = 5; + string account_id = 1; + string related_id = 2; + optional Account account = 3; + optional Account related = 4; + int32 type = 5; google.protobuf.Timestamp created_at = 6; google.protobuf.Timestamp updated_at = 7; } -// Enum for relationship types -enum RelationshipType { - RELATIONSHIP_TYPE_UNSPECIFIED = 0; - FRIEND = 1; - BLOCKED = 2; - PENDING_INCOMING = 3; - PENDING_OUTGOING = 4; -} - // Leveling information message LevelingInfo { int32 current_level = 1; @@ -192,43 +183,30 @@ service AccountService { // Account Operations rpc GetAccount(GetAccountRequest) returns (Account) {} rpc GetAccountBatch(GetAccountBatchRequest) returns (GetAccountBatchResponse) {} - rpc CreateAccount(CreateAccountRequest) returns (Account) {} - rpc UpdateAccount(UpdateAccountRequest) returns (Account) {} - rpc DeleteAccount(DeleteAccountRequest) returns (google.protobuf.Empty) {} rpc ListAccounts(ListAccountsRequest) returns (ListAccountsResponse) {} - + // Profile Operations rpc GetProfile(GetProfileRequest) returns (AccountProfile) {} - rpc UpdateProfile(UpdateProfileRequest) returns (AccountProfile) {} - + // Contact Operations - rpc AddContact(AddContactRequest) returns (AccountContact) {} - rpc UpdateContact(UpdateContactRequest) returns (AccountContact) {} - rpc RemoveContact(RemoveContactRequest) returns (google.protobuf.Empty) {} rpc ListContacts(ListContactsRequest) returns (ListContactsResponse) {} - rpc VerifyContact(VerifyContactRequest) returns (AccountContact) {} - + // Badge Operations - rpc AddBadge(AddBadgeRequest) returns (AccountBadge) {} - rpc RemoveBadge(RemoveBadgeRequest) returns (google.protobuf.Empty) {} rpc ListBadges(ListBadgesRequest) returns (ListBadgesResponse) {} - rpc SetActiveBadge(SetActiveBadgeRequest) returns (AccountProfile) {} - + // Authentication Factor Operations - rpc AddAuthFactor(AddAuthFactorRequest) returns (AccountAuthFactor) {} - rpc RemoveAuthFactor(RemoveAuthFactorRequest) returns (google.protobuf.Empty) {} rpc ListAuthFactors(ListAuthFactorsRequest) returns (ListAuthFactorsResponse) {} - + // Connection Operations - rpc AddConnection(AddConnectionRequest) returns (AccountConnection) {} - rpc RemoveConnection(RemoveConnectionRequest) returns (google.protobuf.Empty) {} rpc ListConnections(ListConnectionsRequest) returns (ListConnectionsResponse) {} - + // Relationship Operations - rpc CreateRelationship(CreateRelationshipRequest) returns (Relationship) {} - rpc UpdateRelationship(UpdateRelationshipRequest) returns (Relationship) {} - rpc DeleteRelationship(DeleteRelationshipRequest) returns (google.protobuf.Empty) {} rpc ListRelationships(ListRelationshipsRequest) returns (ListRelationshipsResponse) {} + + rpc GetRelationship(GetRelationshipRequest) returns (GetRelationshipResponse) {} + rpc HasRelationship(GetRelationshipRequest) returns (google.protobuf.BoolValue) {} + rpc ListFriends(ListUserRelationshipSimpleRequest) returns (ListUserRelationshipSimpleResponse) {} + rpc ListBlocked(ListUserRelationshipSimpleRequest) returns (ListUserRelationshipSimpleResponse) {} } // ==================================== @@ -301,18 +279,6 @@ message AddContactRequest { bool is_primary = 4; // If this should be the primary contact } -message UpdateContactRequest { - string id = 1; // Contact ID to update - string account_id = 2; // Account ID (for validation) - google.protobuf.StringValue content = 3; // New contact content - google.protobuf.BoolValue is_primary = 4; // New primary status -} - -message RemoveContactRequest { - string id = 1; // Contact ID to remove - string account_id = 2; // Account ID (for validation) -} - message ListContactsRequest { string account_id = 1; // Account ID to list contacts for AccountContactType type = 2; // Optional: filter by type @@ -330,20 +296,6 @@ message VerifyContactRequest { } // Badge Requests/Responses -message AddBadgeRequest { - string account_id = 1; // Account to add badge to - string type = 2; // Type of badge - google.protobuf.StringValue label = 3; // Display label - google.protobuf.StringValue caption = 4; // Description - map meta = 5; // Additional metadata - google.protobuf.Timestamp expired_at = 6; // Optional expiration -} - -message RemoveBadgeRequest { - string id = 1; // Badge ID to remove - string account_id = 2; // Account ID (for validation) -} - message ListBadgesRequest { string account_id = 1; // Account to list badges for string type = 2; // Optional: filter by type @@ -354,26 +306,6 @@ message ListBadgesResponse { repeated AccountBadge badges = 1; // List of badges } -message SetActiveBadgeRequest { - string account_id = 1; // Account to update - string badge_id = 2; // Badge ID to set as active (empty to clear) -} - -// Authentication Factor Requests/Responses -message AddAuthFactorRequest { - string account_id = 1; // Account to add factor to - AccountAuthFactorType type = 2; // Type of factor - string secret = 3; // Factor secret (hashed on server) - map config = 4; // Configuration - int32 trustworthy = 5; // Trust level - google.protobuf.Timestamp expired_at = 6; // Optional expiration -} - -message RemoveAuthFactorRequest { - string id = 1; // Factor ID to remove - string account_id = 2; // Account ID (for validation) -} - message ListAuthFactorsRequest { string account_id = 1; // Account to list factors for bool active_only = 2; // Only return active (non-expired) factors @@ -383,21 +315,6 @@ message ListAuthFactorsResponse { repeated AccountAuthFactor factors = 1; // List of auth factors } -// Connection Requests/Responses -message AddConnectionRequest { - string account_id = 1; // Account to add connection to - string provider = 2; // Provider name (e.g., "google", "github") - string provided_identifier = 3; // Provider's user ID - map meta = 4; // Additional metadata - google.protobuf.StringValue access_token = 5; // OAuth access token - google.protobuf.StringValue refresh_token = 6; // OAuth refresh token -} - -message RemoveConnectionRequest { - string id = 1; // Connection ID to remove - string account_id = 2; // Account ID (for validation) -} - message ListConnectionsRequest { string account_id = 1; // Account to list connections for string provider = 2; // Optional: filter by provider @@ -408,30 +325,9 @@ message ListConnectionsResponse { } // Relationship Requests/Responses -message CreateRelationshipRequest { - string from_account_id = 1; // Source account ID - string to_account_id = 2; // Target account ID - RelationshipType type = 3; // Type of relationship - string note = 4; // Optional note -} - -message UpdateRelationshipRequest { - string id = 1; // Relationship ID to update - string account_id = 2; // Account ID (for validation) - RelationshipType type = 3; // New relationship type - google.protobuf.StringValue note = 4; // New note -} - -message DeleteRelationshipRequest { - string id = 1; // Relationship ID to delete - string account_id = 2; // Account ID (for validation) -} - message ListRelationshipsRequest { string account_id = 1; // Account to list relationships for - RelationshipType type = 2; // Optional: filter by type - bool incoming = 3; // If true, list incoming relationships - bool outgoing = 4; // If true, list outgoing relationships + optional int32 status = 2; // Filter by status int32 page_size = 5; // Number of results per page string page_token = 6; // Token for pagination } @@ -442,3 +338,20 @@ message ListRelationshipsResponse { int32 total_size = 3; // Total number of relationships } +message GetRelationshipRequest { + string account_id = 1; + string related_id = 2; + optional int32 status = 3; +} + +message GetRelationshipResponse { + optional Relationship relationship = 1; +} + +message ListUserRelationshipSimpleRequest { + string account_id = 1; +} + +message ListUserRelationshipSimpleResponse { + repeated string accounts_id = 1; +} \ No newline at end of file diff --git a/DysonNetwork.Shared/Proto/auth.proto b/DysonNetwork.Shared/Proto/auth.proto index 2b70f8c..6c4216f 100644 --- a/DysonNetwork.Shared/Proto/auth.proto +++ b/DysonNetwork.Shared/Proto/auth.proto @@ -65,6 +65,9 @@ enum ChallengePlatform { service AuthService { rpc Authenticate(AuthenticateRequest) returns (AuthenticateResponse) {} + + rpc ValidatePin(ValidatePinRequest) returns (ValidateResponse) {} + rpc ValidateCaptcha(ValidateCaptchaRequest) returns (ValidateResponse) {} } message AuthenticateRequest { @@ -77,6 +80,19 @@ message AuthenticateResponse { optional AuthSession session = 3; } +message ValidatePinRequest { + string account_id = 1; + string pin = 2; +} + +message ValidateCaptchaRequest { + string token = 1; +} + +message ValidateResponse { + bool valid = 1; +} + // Permission related messages and services message PermissionNode { string id = 1; diff --git a/DysonNetwork.Shared/Proto/file.proto b/DysonNetwork.Shared/Proto/file.proto index 8ba4d8e..1040410 100644 --- a/DysonNetwork.Shared/Proto/file.proto +++ b/DysonNetwork.Shared/Proto/file.proto @@ -51,6 +51,7 @@ message CloudFile { service FileService { // Get file reference by ID rpc GetFile(GetFileRequest) returns (CloudFile); + rpc GetFileBatch(GetFileBatchRequest) returns (GetFileBatchResponse); // Update an existing file reference rpc UpdateFile(UpdateFileRequest) returns (CloudFile); @@ -73,6 +74,14 @@ message GetFileRequest { string id = 1; } +message GetFileBatchRequest { + repeated string ids = 1; +} + +message GetFileBatchResponse { + repeated CloudFile files = 1; +} + // Request message for UpdateFile message UpdateFileRequest { CloudFile file = 1; @@ -157,6 +166,18 @@ message CreateReferenceRequest { optional google.protobuf.Duration duration = 5; // Alternative to expired_at } +message CreateReferenceBatchRequest { + repeated string files_id = 1; + string usage = 2; + string resource_id = 3; + optional google.protobuf.Timestamp expired_at = 4; + optional google.protobuf.Duration duration = 5; // Alternative to expired_at +} + +message CreateReferenceBatchResponse { + repeated CloudFileReference references = 1; +} + message GetReferencesRequest { string file_id = 1; } @@ -239,6 +260,7 @@ message HasFileReferencesResponse { service FileReferenceService { // Creates a new reference to a file for a specific resource rpc CreateReference(CreateReferenceRequest) returns (CloudFileReference); + rpc CreateReferenceBatch(CreateReferenceBatchRequest) returns (CreateReferenceBatchResponse); // Gets all references to a file rpc GetReferences(GetReferencesRequest) returns (GetReferencesResponse); diff --git a/DysonNetwork.Shared/Registry/ServiceHelper.cs b/DysonNetwork.Shared/Registry/ServiceHelper.cs index 6b2f18b..cf67ccb 100644 --- a/DysonNetwork.Shared/Registry/ServiceHelper.cs +++ b/DysonNetwork.Shared/Registry/ServiceHelper.cs @@ -44,4 +44,37 @@ public static class ServiceHelper return services; } + + public static IServiceCollection AddDriveService(this IServiceCollection services) + { + services.AddSingleton(sp => + { + var etcdClient = sp.GetRequiredService(); + var config = sp.GetRequiredService(); + var clientCertPath = config["Service:ClientCert"]!; + var clientKeyPath = config["Service:ClientKey"]!; + var clientCertPassword = config["Service:CertPassword"]; + + return GrpcClientHelper + .CreateFileServiceClient(etcdClient, clientCertPath, clientKeyPath, clientCertPassword) + .GetAwaiter() + .GetResult(); + }); + + services.AddSingleton(sp => + { + var etcdClient = sp.GetRequiredService(); + var config = sp.GetRequiredService(); + var clientCertPath = config["Service:ClientCert"]!; + var clientKeyPath = config["Service:ClientKey"]!; + var clientCertPassword = config["Service:CertPassword"]; + + return GrpcClientHelper + .CreateFileReferenceServiceClient(etcdClient, clientCertPath, clientKeyPath, clientCertPassword) + .GetAwaiter() + .GetResult(); + }); + + return services; + } } \ No newline at end of file diff --git a/DysonNetwork.Sphere/Account/AbuseReport.cs b/DysonNetwork.Sphere/Account/AbuseReport.cs deleted file mode 100644 index 87c1be1..0000000 --- a/DysonNetwork.Sphere/Account/AbuseReport.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using NodaTime; - -namespace DysonNetwork.Sphere.Account; - -public enum AbuseReportType -{ - Copyright, - Harassment, - Impersonation, - OffensiveContent, - Spam, - PrivacyViolation, - IllegalContent, - Other -} - -public class AbuseReport : ModelBase -{ - public Guid Id { get; set; } = Guid.NewGuid(); - [MaxLength(4096)] public string ResourceIdentifier { get; set; } = null!; - public AbuseReportType Type { get; set; } - [MaxLength(8192)] public string Reason { get; set; } = null!; - - public Instant? ResolvedAt { get; set; } - [MaxLength(8192)] public string? Resolution { get; set; } - - public Guid AccountId { get; set; } - public Account Account { get; set; } = null!; -} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Account/Account.cs b/DysonNetwork.Sphere/Account/Account.cs deleted file mode 100644 index 5d05364..0000000 --- a/DysonNetwork.Sphere/Account/Account.cs +++ /dev/null @@ -1,196 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; -using System.Text.Json.Serialization; -using DysonNetwork.Sphere.Permission; -using DysonNetwork.Sphere.Storage; -using DysonNetwork.Sphere.Wallet; -using Microsoft.EntityFrameworkCore; -using NodaTime; -using OtpNet; - -namespace DysonNetwork.Sphere.Account; - -[Index(nameof(Name), IsUnique = true)] -public class Account : ModelBase -{ - public Guid Id { get; set; } - [MaxLength(256)] public string Name { get; set; } = string.Empty; - [MaxLength(256)] public string Nick { get; set; } = string.Empty; - [MaxLength(32)] public string Language { get; set; } = string.Empty; - public Instant? ActivatedAt { get; set; } - public bool IsSuperuser { get; set; } = false; - - public Profile Profile { get; set; } = null!; - public ICollection Contacts { get; set; } = new List(); - public ICollection Badges { get; set; } = new List(); - - [JsonIgnore] public ICollection AuthFactors { get; set; } = new List(); - [JsonIgnore] public ICollection Connections { get; set; } = new List(); - [JsonIgnore] public ICollection Sessions { get; set; } = new List(); - [JsonIgnore] public ICollection Challenges { get; set; } = new List(); - - [JsonIgnore] public ICollection OutgoingRelationships { get; set; } = new List(); - [JsonIgnore] public ICollection IncomingRelationships { get; set; } = new List(); - - [JsonIgnore] public ICollection Subscriptions { get; set; } = new List(); -} - -public abstract class Leveling -{ - public static readonly List ExperiencePerLevel = - [ - 0, // Level 0 - 100, // Level 1 - 250, // Level 2 - 500, // Level 3 - 1000, // Level 4 - 2000, // Level 5 - 4000, // Level 6 - 8000, // Level 7 - 16000, // Level 8 - 32000, // Level 9 - 64000, // Level 10 - 128000, // Level 11 - 256000, // Level 12 - 512000, // Level 13 - 1024000 // Level 14 - ]; -} - -public class Profile : ModelBase -{ - public Guid Id { get; set; } - [MaxLength(256)] public string? FirstName { get; set; } - [MaxLength(256)] public string? MiddleName { get; set; } - [MaxLength(256)] public string? LastName { get; set; } - [MaxLength(4096)] public string? Bio { get; set; } - [MaxLength(1024)] public string? Gender { get; set; } - [MaxLength(1024)] public string? Pronouns { get; set; } - [MaxLength(1024)] public string? TimeZone { get; set; } - [MaxLength(1024)] public string? Location { get; set; } - public Instant? Birthday { get; set; } - public Instant? LastSeenAt { get; set; } - - [Column(TypeName = "jsonb")] public VerificationMark? Verification { get; set; } - [Column(TypeName = "jsonb")] public BadgeReferenceObject? ActiveBadge { get; set; } - [Column(TypeName = "jsonb")] public SubscriptionReferenceObject? StellarMembership { get; set; } - - public int Experience { get; set; } = 0; - [NotMapped] public int Level => Leveling.ExperiencePerLevel.Count(xp => Experience >= xp) - 1; - - [NotMapped] - public double LevelingProgress => Level >= Leveling.ExperiencePerLevel.Count - 1 - ? 100 - : (Experience - Leveling.ExperiencePerLevel[Level]) * 100.0 / - (Leveling.ExperiencePerLevel[Level + 1] - Leveling.ExperiencePerLevel[Level]); - - // Outdated fields, for backward compability - [MaxLength(32)] public string? PictureId { get; set; } - [MaxLength(32)] public string? BackgroundId { get; set; } - - [Column(TypeName = "jsonb")] public CloudFileReferenceObject? Picture { get; set; } - [Column(TypeName = "jsonb")] public CloudFileReferenceObject? Background { get; set; } - - public Guid AccountId { get; set; } - [JsonIgnore] public Account Account { get; set; } = null!; -} - -public class AccountContact : ModelBase -{ - public Guid Id { get; set; } - public AccountContactType Type { get; set; } - public Instant? VerifiedAt { get; set; } - public bool IsPrimary { get; set; } = false; - [MaxLength(1024)] public string Content { get; set; } = string.Empty; - - public Guid AccountId { get; set; } - [JsonIgnore] public Account Account { get; set; } = null!; -} - -public enum AccountContactType -{ - Email, - PhoneNumber, - Address -} - -public class AccountAuthFactor : ModelBase -{ - public Guid Id { get; set; } - public AccountAuthFactorType Type { get; set; } - [JsonIgnore] [MaxLength(8196)] public string? Secret { get; set; } - - [JsonIgnore] - [Column(TypeName = "jsonb")] - public Dictionary? Config { get; set; } = new(); - - /// - /// The trustworthy stands for how safe is this auth factor. - /// Basically, it affects how many steps it can complete in authentication. - /// Besides, users may need to use some high-trustworthy level auth factors when confirming some dangerous operations. - /// - public int Trustworthy { get; set; } = 1; - - public Instant? EnabledAt { get; set; } - public Instant? ExpiredAt { get; set; } - - public Guid AccountId { get; set; } - [JsonIgnore] public Account Account { get; set; } = null!; - - public AccountAuthFactor HashSecret(int cost = 12) - { - if (Secret == null) return this; - Secret = BCrypt.Net.BCrypt.HashPassword(Secret, workFactor: cost); - return this; - } - - public bool VerifyPassword(string password) - { - if (Secret == null) - throw new InvalidOperationException("Auth factor with no secret cannot be verified with password."); - switch (Type) - { - case AccountAuthFactorType.Password: - case AccountAuthFactorType.PinCode: - return BCrypt.Net.BCrypt.Verify(password, Secret); - case AccountAuthFactorType.TimedCode: - var otp = new Totp(Base32Encoding.ToBytes(Secret)); - return otp.VerifyTotp(DateTime.UtcNow, password, out _, new VerificationWindow(previous: 5, future: 5)); - case AccountAuthFactorType.EmailCode: - case AccountAuthFactorType.InAppCode: - default: - throw new InvalidOperationException("Unsupported verification type, use CheckDeliveredCode instead."); - } - } - - /// - /// This dictionary will be returned to the client and should only be set when it just created. - /// Useful for passing the client some data to finishing setup and recovery code. - /// - [NotMapped] - public Dictionary? CreatedResponse { get; set; } -} - -public enum AccountAuthFactorType -{ - Password, - EmailCode, - InAppCode, - TimedCode, - PinCode, -} - -public class AccountConnection : ModelBase -{ - public Guid Id { get; set; } = Guid.NewGuid(); - [MaxLength(4096)] public string Provider { get; set; } = null!; - [MaxLength(8192)] public string ProvidedIdentifier { get; set; } = null!; - [Column(TypeName = "jsonb")] public Dictionary? Meta { get; set; } = new(); - - [JsonIgnore] [MaxLength(4096)] public string? AccessToken { get; set; } - [JsonIgnore] [MaxLength(4096)] public string? RefreshToken { get; set; } - public Instant? LastUsedAt { get; set; } - - public Guid AccountId { get; set; } - public Account Account { get; set; } = null!; -} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Account/AccountController.cs b/DysonNetwork.Sphere/Account/AccountController.cs deleted file mode 100644 index 1e68f22..0000000 --- a/DysonNetwork.Sphere/Account/AccountController.cs +++ /dev/null @@ -1,177 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using DysonNetwork.Sphere.Auth; -using DysonNetwork.Sphere.Permission; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; -using NodaTime; -using NodaTime.Extensions; -using System.Collections.Generic; - -namespace DysonNetwork.Sphere.Account; - -[ApiController] -[Route("/api/accounts")] -public class AccountController( - AppDatabase db, - AuthService auth, - AccountService accounts, - AccountEventService events -) : ControllerBase -{ - [HttpGet("{name}")] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task> GetByName(string name) - { - var account = await db.Accounts - .Include(e => e.Badges) - .Include(e => e.Profile) - .Where(a => a.Name == name) - .FirstOrDefaultAsync(); - return account is null ? new NotFoundResult() : account; - } - - [HttpGet("{name}/badges")] - [ProducesResponseType>(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task>> GetBadgesByName(string name) - { - var account = await db.Accounts - .Include(e => e.Badges) - .Where(a => a.Name == name) - .FirstOrDefaultAsync(); - return account is null ? NotFound() : account.Badges.ToList(); - } - - public class AccountCreateRequest - { - [Required] - [MinLength(2)] - [MaxLength(256)] - [RegularExpression(@"^[A-Za-z0-9_-]+$", - ErrorMessage = "Name can only contain letters, numbers, underscores, and hyphens.") - ] - public string Name { get; set; } = string.Empty; - - [Required] [MaxLength(256)] public string Nick { get; set; } = string.Empty; - - [EmailAddress] - [RegularExpression(@"^[^+]+@[^@]+\.[^@]+$", ErrorMessage = "Email address cannot contain '+' symbol.")] - [Required] - [MaxLength(1024)] - public string Email { get; set; } = string.Empty; - - [Required] - [MinLength(4)] - [MaxLength(128)] - public string Password { get; set; } = string.Empty; - - [MaxLength(128)] public string Language { get; set; } = "en-us"; - - [Required] public string CaptchaToken { get; set; } = string.Empty; - } - - [HttpPost] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - public async Task> CreateAccount([FromBody] AccountCreateRequest request) - { - if (!await auth.ValidateCaptcha(request.CaptchaToken)) return BadRequest("Invalid captcha token."); - - try - { - var account = await accounts.CreateAccount( - request.Name, - request.Nick, - request.Email, - request.Password, - request.Language - ); - return Ok(account); - } - catch (Exception ex) - { - return BadRequest(ex.Message); - } - } - - public class RecoveryPasswordRequest - { - [Required] public string Account { get; set; } = null!; - [Required] public string CaptchaToken { get; set; } = null!; - } - - [HttpPost("recovery/password")] - public async Task RequestResetPassword([FromBody] RecoveryPasswordRequest request) - { - if (!await auth.ValidateCaptcha(request.CaptchaToken)) return BadRequest("Invalid captcha token."); - - var account = await accounts.LookupAccount(request.Account); - if (account is null) return BadRequest("Unable to find the account."); - - try - { - await accounts.RequestPasswordReset(account); - } - catch (InvalidOperationException) - { - return BadRequest("You already requested password reset within 24 hours."); - } - - return Ok(); - } - - public class StatusRequest - { - public StatusAttitude Attitude { get; set; } - public bool IsInvisible { get; set; } - public bool IsNotDisturb { get; set; } - [MaxLength(1024)] public string? Label { get; set; } - public Instant? ClearedAt { get; set; } - } - - [HttpGet("{name}/statuses")] - public async Task> GetOtherStatus(string name) - { - var account = await db.Accounts.FirstOrDefaultAsync(a => a.Name == name); - if (account is null) return BadRequest(); - var status = await events.GetStatus(account.Id); - status.IsInvisible = false; // Keep the invisible field not available for other users - return Ok(status); - } - - [HttpGet("{name}/calendar")] - public async Task>> GetOtherEventCalendar( - string name, - [FromQuery] int? month, - [FromQuery] int? year - ) - { - var currentDate = SystemClock.Instance.GetCurrentInstant().InUtc().Date; - month ??= currentDate.Month; - year ??= currentDate.Year; - - if (month is < 1 or > 12) return BadRequest("Invalid month."); - if (year < 1) return BadRequest("Invalid year."); - - var account = await db.Accounts.FirstOrDefaultAsync(a => a.Name == name); - if (account is null) return BadRequest(); - - var calendar = await events.GetEventCalendar(account, month.Value, year.Value, replaceInvisible: true); - return Ok(calendar); - } - - [HttpGet("search")] - public async Task> Search([FromQuery] string query, [FromQuery] int take = 20) - { - if (string.IsNullOrWhiteSpace(query)) - return []; - return await db.Accounts - .Include(e => e.Profile) - .Where(a => EF.Functions.ILike(a.Name, $"%{query}%") || - EF.Functions.ILike(a.Nick, $"%{query}%")) - .Take(take) - .ToListAsync(); - } -} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Account/AccountCurrentController.cs b/DysonNetwork.Sphere/Account/AccountCurrentController.cs deleted file mode 100644 index 2230006..0000000 --- a/DysonNetwork.Sphere/Account/AccountCurrentController.cs +++ /dev/null @@ -1,703 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using DysonNetwork.Sphere.Auth; -using DysonNetwork.Sphere.Permission; -using DysonNetwork.Sphere.Storage; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; -using NodaTime; -using Org.BouncyCastle.Utilities; - -namespace DysonNetwork.Sphere.Account; - -[Authorize] -[ApiController] -[Route("/api/accounts/me")] -public class AccountCurrentController( - AppDatabase db, - AccountService accounts, - FileReferenceService fileRefService, - AccountEventService events, - AuthService auth -) : ControllerBase -{ - [HttpGet] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task> GetCurrentIdentity() - { - if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); - var userId = currentUser.Id; - - var account = await db.Accounts - .Include(e => e.Badges) - .Include(e => e.Profile) - .Where(e => e.Id == userId) - .FirstOrDefaultAsync(); - - return Ok(account); - } - - public class BasicInfoRequest - { - [MaxLength(256)] public string? Nick { get; set; } - [MaxLength(32)] public string? Language { get; set; } - } - - [HttpPatch] - public async Task> UpdateBasicInfo([FromBody] BasicInfoRequest request) - { - if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); - - var account = await db.Accounts.FirstAsync(a => a.Id == currentUser.Id); - - if (request.Nick is not null) account.Nick = request.Nick; - if (request.Language is not null) account.Language = request.Language; - - await db.SaveChangesAsync(); - await accounts.PurgeAccountCache(currentUser); - return currentUser; - } - - public class ProfileRequest - { - [MaxLength(256)] public string? FirstName { get; set; } - [MaxLength(256)] public string? MiddleName { get; set; } - [MaxLength(256)] public string? LastName { get; set; } - [MaxLength(1024)] public string? Gender { get; set; } - [MaxLength(1024)] public string? Pronouns { get; set; } - [MaxLength(1024)] public string? TimeZone { get; set; } - [MaxLength(1024)] public string? Location { get; set; } - [MaxLength(4096)] public string? Bio { get; set; } - public Instant? Birthday { get; set; } - - [MaxLength(32)] public string? PictureId { get; set; } - [MaxLength(32)] public string? BackgroundId { get; set; } - } - - [HttpPatch("profile")] - public async Task> UpdateProfile([FromBody] ProfileRequest request) - { - if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); - var userId = currentUser.Id; - - var profile = await db.AccountProfiles - .Where(p => p.Account.Id == userId) - .FirstOrDefaultAsync(); - if (profile is null) return BadRequest("Unable to get your account."); - - if (request.FirstName is not null) profile.FirstName = request.FirstName; - if (request.MiddleName is not null) profile.MiddleName = request.MiddleName; - if (request.LastName is not null) profile.LastName = request.LastName; - if (request.Bio is not null) profile.Bio = request.Bio; - if (request.Gender is not null) profile.Gender = request.Gender; - if (request.Pronouns is not null) profile.Pronouns = request.Pronouns; - if (request.Birthday is not null) profile.Birthday = request.Birthday; - if (request.Location is not null) profile.Location = request.Location; - if (request.TimeZone is not null) profile.TimeZone = request.TimeZone; - - if (request.PictureId is not null) - { - var picture = await db.Files.Where(f => f.Id == request.PictureId).FirstOrDefaultAsync(); - if (picture is null) return BadRequest("Invalid picture id, unable to find the file on cloud."); - - var profileResourceId = $"profile:{profile.Id}"; - - // Remove old references for the profile picture - if (profile.Picture is not null) - { - var oldPictureRefs = - await fileRefService.GetResourceReferencesAsync(profileResourceId, "profile.picture"); - foreach (var oldRef in oldPictureRefs) - { - await fileRefService.DeleteReferenceAsync(oldRef.Id); - } - } - - profile.Picture = picture.ToReferenceObject(); - - // Create new reference - await fileRefService.CreateReferenceAsync( - picture.Id, - "profile.picture", - profileResourceId - ); - } - - if (request.BackgroundId is not null) - { - var background = await db.Files.Where(f => f.Id == request.BackgroundId).FirstOrDefaultAsync(); - if (background is null) return BadRequest("Invalid background id, unable to find the file on cloud."); - - var profileResourceId = $"profile:{profile.Id}"; - - // Remove old references for the profile background - if (profile.Background is not null) - { - var oldBackgroundRefs = - await fileRefService.GetResourceReferencesAsync(profileResourceId, "profile.background"); - foreach (var oldRef in oldBackgroundRefs) - { - await fileRefService.DeleteReferenceAsync(oldRef.Id); - } - } - - profile.Background = background.ToReferenceObject(); - - // Create new reference - await fileRefService.CreateReferenceAsync( - background.Id, - "profile.background", - profileResourceId - ); - } - - db.Update(profile); - await db.SaveChangesAsync(); - - await accounts.PurgeAccountCache(currentUser); - - return profile; - } - - [HttpDelete] - public async Task RequestDeleteAccount() - { - if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); - - try - { - await accounts.RequestAccountDeletion(currentUser); - } - catch (InvalidOperationException) - { - return BadRequest("You already requested account deletion within 24 hours."); - } - - return Ok(); - } - - [HttpGet("statuses")] - public async Task> GetCurrentStatus() - { - if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); - var status = await events.GetStatus(currentUser.Id); - return Ok(status); - } - - [HttpPatch("statuses")] - [RequiredPermission("global", "accounts.statuses.update")] - public async Task> UpdateStatus([FromBody] AccountController.StatusRequest request) - { - if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); - - var now = SystemClock.Instance.GetCurrentInstant(); - var status = await db.AccountStatuses - .Where(e => e.AccountId == currentUser.Id) - .Where(e => e.ClearedAt == null || e.ClearedAt > now) - .OrderByDescending(e => e.CreatedAt) - .FirstOrDefaultAsync(); - if (status is null) return NotFound(); - - status.Attitude = request.Attitude; - status.IsInvisible = request.IsInvisible; - status.IsNotDisturb = request.IsNotDisturb; - status.Label = request.Label; - status.ClearedAt = request.ClearedAt; - - db.Update(status); - await db.SaveChangesAsync(); - events.PurgeStatusCache(currentUser.Id); - - return status; - } - - [HttpPost("statuses")] - [RequiredPermission("global", "accounts.statuses.create")] - public async Task> CreateStatus([FromBody] AccountController.StatusRequest request) - { - if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); - - var status = new Status - { - AccountId = currentUser.Id, - Attitude = request.Attitude, - IsInvisible = request.IsInvisible, - IsNotDisturb = request.IsNotDisturb, - Label = request.Label, - ClearedAt = request.ClearedAt - }; - - return await events.CreateStatus(currentUser, status); - } - - [HttpDelete("me/statuses")] - public async Task DeleteStatus() - { - if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); - - var now = SystemClock.Instance.GetCurrentInstant(); - var status = await db.AccountStatuses - .Where(s => s.AccountId == currentUser.Id) - .Where(s => s.ClearedAt == null || s.ClearedAt > now) - .OrderByDescending(s => s.CreatedAt) - .FirstOrDefaultAsync(); - if (status is null) return NotFound(); - - await events.ClearStatus(currentUser, status); - return NoContent(); - } - - [HttpGet("check-in")] - public async Task> GetCheckInResult() - { - if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); - var userId = currentUser.Id; - - var now = SystemClock.Instance.GetCurrentInstant(); - var today = now.InUtc().Date; - var startOfDay = today.AtStartOfDayInZone(DateTimeZone.Utc).ToInstant(); - var endOfDay = today.PlusDays(1).AtStartOfDayInZone(DateTimeZone.Utc).ToInstant(); - - var result = await db.AccountCheckInResults - .Where(x => x.AccountId == userId) - .Where(x => x.CreatedAt >= startOfDay && x.CreatedAt < endOfDay) - .OrderByDescending(x => x.CreatedAt) - .FirstOrDefaultAsync(); - - return result is null ? NotFound() : Ok(result); - } - - [HttpPost("check-in")] - public async Task> DoCheckIn([FromBody] string? captchaToken) - { - if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); - - var isAvailable = await events.CheckInDailyIsAvailable(currentUser); - if (!isAvailable) - return BadRequest("Check-in is not available for today."); - - try - { - var needsCaptcha = await events.CheckInDailyDoAskCaptcha(currentUser); - return needsCaptcha switch - { - true when string.IsNullOrWhiteSpace(captchaToken) => StatusCode(423, - "Captcha is required for this check-in."), - true when !await auth.ValidateCaptcha(captchaToken!) => BadRequest("Invalid captcha token."), - _ => await events.CheckInDaily(currentUser) - }; - } - catch (InvalidOperationException ex) - { - return BadRequest(ex.Message); - } - } - - [HttpGet("calendar")] - public async Task>> GetEventCalendar([FromQuery] int? month, - [FromQuery] int? year) - { - if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); - - var currentDate = SystemClock.Instance.GetCurrentInstant().InUtc().Date; - month ??= currentDate.Month; - year ??= currentDate.Year; - - if (month is < 1 or > 12) return BadRequest("Invalid month."); - if (year < 1) return BadRequest("Invalid year."); - - var calendar = await events.GetEventCalendar(currentUser, month.Value, year.Value); - return Ok(calendar); - } - - [HttpGet("actions")] - [ProducesResponseType>(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status401Unauthorized)] - public async Task>> GetActionLogs( - [FromQuery] int take = 20, - [FromQuery] int offset = 0 - ) - { - if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); - - var query = db.ActionLogs - .Where(log => log.AccountId == currentUser.Id) - .OrderByDescending(log => log.CreatedAt); - - var total = await query.CountAsync(); - Response.Headers.Append("X-Total", total.ToString()); - - var logs = await query - .Skip(offset) - .Take(take) - .ToListAsync(); - - return Ok(logs); - } - - [HttpGet("factors")] - public async Task>> GetAuthFactors() - { - if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); - - var factors = await db.AccountAuthFactors - .Include(f => f.Account) - .Where(f => f.Account.Id == currentUser.Id) - .ToListAsync(); - - return Ok(factors); - } - - public class AuthFactorRequest - { - public AccountAuthFactorType Type { get; set; } - public string? Secret { get; set; } - } - - [HttpPost("factors")] - [Authorize] - public async Task> CreateAuthFactor([FromBody] AuthFactorRequest request) - { - if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); - if (await accounts.CheckAuthFactorExists(currentUser, request.Type)) - return BadRequest($"Auth factor with type {request.Type} is already exists."); - - var factor = await accounts.CreateAuthFactor(currentUser, request.Type, request.Secret); - return Ok(factor); - } - - [HttpPost("factors/{id:guid}/enable")] - [Authorize] - public async Task> EnableAuthFactor(Guid id, [FromBody] string? code) - { - if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); - - var factor = await db.AccountAuthFactors - .Where(f => f.AccountId == currentUser.Id && f.Id == id) - .FirstOrDefaultAsync(); - if (factor is null) return NotFound(); - - try - { - factor = await accounts.EnableAuthFactor(factor, code); - return Ok(factor); - } - catch (Exception ex) - { - return BadRequest(ex.Message); - } - } - - [HttpPost("factors/{id:guid}/disable")] - [Authorize] - public async Task> DisableAuthFactor(Guid id) - { - if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); - - var factor = await db.AccountAuthFactors - .Where(f => f.AccountId == currentUser.Id && f.Id == id) - .FirstOrDefaultAsync(); - if (factor is null) return NotFound(); - - try - { - factor = await accounts.DisableAuthFactor(factor); - return Ok(factor); - } - catch (Exception ex) - { - return BadRequest(ex.Message); - } - } - - [HttpDelete("factors/{id:guid}")] - [Authorize] - public async Task> DeleteAuthFactor(Guid id) - { - if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); - - var factor = await db.AccountAuthFactors - .Where(f => f.AccountId == currentUser.Id && f.Id == id) - .FirstOrDefaultAsync(); - if (factor is null) return NotFound(); - - try - { - await accounts.DeleteAuthFactor(factor); - return NoContent(); - } - catch (Exception ex) - { - return BadRequest(ex.Message); - } - } - - public class AuthorizedDevice - { - public string? Label { get; set; } - public string UserAgent { get; set; } = null!; - public string DeviceId { get; set; } = null!; - public ChallengePlatform Platform { get; set; } - public List Sessions { get; set; } = []; - } - - [HttpGet("devices")] - [Authorize] - public async Task>> GetDevices() - { - if (HttpContext.Items["CurrentUser"] is not Account currentUser || - HttpContext.Items["CurrentSession"] is not Session currentSession) return Unauthorized(); - - Response.Headers.Append("X-Auth-Session", currentSession.Id.ToString()); - - // Group sessions by the related DeviceId, then create an AuthorizedDevice for each group. - var deviceGroups = await db.AuthSessions - .Where(s => s.Account.Id == currentUser.Id) - .Include(s => s.Challenge) - .GroupBy(s => s.Challenge.DeviceId!) - .Select(g => new AuthorizedDevice - { - DeviceId = g.Key!, - UserAgent = g.First(x => x.Challenge.UserAgent != null).Challenge.UserAgent!, - Platform = g.First().Challenge.Platform!, - Label = g.Where(x => !string.IsNullOrWhiteSpace(x.Label)).Select(x => x.Label).FirstOrDefault(), - Sessions = g - .OrderByDescending(x => x.LastGrantedAt) - .ToList() - }) - .ToListAsync(); - deviceGroups = deviceGroups - .OrderByDescending(s => s.Sessions.First().LastGrantedAt) - .ToList(); - - return Ok(deviceGroups); - } - - [HttpGet("sessions")] - [Authorize] - public async Task>> GetSessions( - [FromQuery] int take = 20, - [FromQuery] int offset = 0 - ) - { - if (HttpContext.Items["CurrentUser"] is not Account currentUser || - HttpContext.Items["CurrentSession"] is not Session currentSession) return Unauthorized(); - - var query = db.AuthSessions - .Include(session => session.Account) - .Include(session => session.Challenge) - .Where(session => session.Account.Id == currentUser.Id); - - var total = await query.CountAsync(); - Response.Headers.Append("X-Total", total.ToString()); - Response.Headers.Append("X-Auth-Session", currentSession.Id.ToString()); - - var sessions = await query - .OrderByDescending(x => x.LastGrantedAt) - .Skip(offset) - .Take(take) - .ToListAsync(); - - return Ok(sessions); - } - - [HttpDelete("sessions/{id:guid}")] - [Authorize] - public async Task> DeleteSession(Guid id) - { - if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); - - try - { - await accounts.DeleteSession(currentUser, id); - return NoContent(); - } - catch (Exception ex) - { - return BadRequest(ex.Message); - } - } - - [HttpDelete("sessions/current")] - [Authorize] - public async Task> DeleteCurrentSession() - { - if (HttpContext.Items["CurrentUser"] is not Account currentUser || - HttpContext.Items["CurrentSession"] is not Session currentSession) return Unauthorized(); - - try - { - await accounts.DeleteSession(currentUser, currentSession.Id); - return NoContent(); - } - catch (Exception ex) - { - return BadRequest(ex.Message); - } - } - - [HttpPatch("sessions/{id:guid}/label")] - public async Task> UpdateSessionLabel(Guid id, [FromBody] string label) - { - if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); - - try - { - await accounts.UpdateSessionLabel(currentUser, id, label); - return NoContent(); - } - catch (Exception ex) - { - return BadRequest(ex.Message); - } - } - - [HttpPatch("sessions/current/label")] - public async Task> UpdateCurrentSessionLabel([FromBody] string label) - { - if (HttpContext.Items["CurrentUser"] is not Account currentUser || - HttpContext.Items["CurrentSession"] is not Session currentSession) return Unauthorized(); - - try - { - await accounts.UpdateSessionLabel(currentUser, currentSession.Id, label); - return NoContent(); - } - catch (Exception ex) - { - return BadRequest(ex.Message); - } - } - - [HttpGet("contacts")] - [Authorize] - public async Task>> GetContacts() - { - if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); - - var contacts = await db.AccountContacts - .Where(c => c.AccountId == currentUser.Id) - .ToListAsync(); - - return Ok(contacts); - } - - public class AccountContactRequest - { - [Required] public AccountContactType Type { get; set; } - [Required] public string Content { get; set; } = null!; - } - - [HttpPost("contacts")] - [Authorize] - public async Task> CreateContact([FromBody] AccountContactRequest request) - { - if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); - - try - { - var contact = await accounts.CreateContactMethod(currentUser, request.Type, request.Content); - return Ok(contact); - } - catch (Exception ex) - { - return BadRequest(ex.Message); - } - } - - [HttpPost("contacts/{id:guid}/verify")] - [Authorize] - public async Task> VerifyContact(Guid id) - { - if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); - - var contact = await db.AccountContacts - .Where(c => c.AccountId == currentUser.Id && c.Id == id) - .FirstOrDefaultAsync(); - if (contact is null) return NotFound(); - - try - { - await accounts.VerifyContactMethod(currentUser, contact); - return Ok(contact); - } - catch (Exception ex) - { - return BadRequest(ex.Message); - } - } - - [HttpPost("contacts/{id:guid}/primary")] - [Authorize] - public async Task> SetPrimaryContact(Guid id) - { - if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); - - var contact = await db.AccountContacts - .Where(c => c.AccountId == currentUser.Id && c.Id == id) - .FirstOrDefaultAsync(); - if (contact is null) return NotFound(); - - try - { - contact = await accounts.SetContactMethodPrimary(currentUser, contact); - return Ok(contact); - } - catch (Exception ex) - { - return BadRequest(ex.Message); - } - } - - [HttpDelete("contacts/{id:guid}")] - [Authorize] - public async Task> DeleteContact(Guid id) - { - if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); - - var contact = await db.AccountContacts - .Where(c => c.AccountId == currentUser.Id && c.Id == id) - .FirstOrDefaultAsync(); - if (contact is null) return NotFound(); - - try - { - await accounts.DeleteContactMethod(currentUser, contact); - return NoContent(); - } - catch (Exception ex) - { - return BadRequest(ex.Message); - } - } - - [HttpGet("badges")] - [ProducesResponseType>(StatusCodes.Status200OK)] - [Authorize] - public async Task>> GetBadges() - { - if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); - - var badges = await db.Badges - .Where(b => b.AccountId == currentUser.Id) - .ToListAsync(); - return Ok(badges); - } - - [HttpPost("badges/{id:guid}/active")] - [Authorize] - public async Task> ActivateBadge(Guid id) - { - if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); - - try - { - await accounts.ActiveBadge(currentUser, id); - return Ok(); - } - catch (Exception ex) - { - return BadRequest(ex.Message); - } - } -} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Account/AccountEventService.cs b/DysonNetwork.Sphere/Account/AccountEventService.cs deleted file mode 100644 index 87e5bb3..0000000 --- a/DysonNetwork.Sphere/Account/AccountEventService.cs +++ /dev/null @@ -1,339 +0,0 @@ -using System.Globalization; -using DysonNetwork.Sphere.Activity; -using DysonNetwork.Sphere.Connection; -using DysonNetwork.Sphere.Storage; -using DysonNetwork.Sphere.Wallet; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Caching.Distributed; -using Microsoft.Extensions.Localization; -using NodaTime; -using Org.BouncyCastle.Asn1.X509; - -namespace DysonNetwork.Sphere.Account; - -public class AccountEventService( - AppDatabase db, - WebSocketService ws, - ICacheService cache, - PaymentService payment, - IStringLocalizer localizer -) -{ - private static readonly Random Random = new(); - private const string StatusCacheKey = "AccountStatus_"; - - public void PurgeStatusCache(Guid userId) - { - var cacheKey = $"{StatusCacheKey}{userId}"; - cache.RemoveAsync(cacheKey); - } - - public async Task GetStatus(Guid userId) - { - var cacheKey = $"{StatusCacheKey}{userId}"; - var cachedStatus = await cache.GetAsync(cacheKey); - if (cachedStatus is not null) - { - cachedStatus!.IsOnline = !cachedStatus.IsInvisible && ws.GetAccountIsConnected(userId); - return cachedStatus; - } - - var now = SystemClock.Instance.GetCurrentInstant(); - var status = await db.AccountStatuses - .Where(e => e.AccountId == userId) - .Where(e => e.ClearedAt == null || e.ClearedAt > now) - .OrderByDescending(e => e.CreatedAt) - .FirstOrDefaultAsync(); - var isOnline = ws.GetAccountIsConnected(userId); - if (status is not null) - { - status.IsOnline = !status.IsInvisible && isOnline; - await cache.SetWithGroupsAsync(cacheKey, status, [$"{AccountService.AccountCachePrefix}{status.AccountId}"], - TimeSpan.FromMinutes(5)); - return status; - } - - if (isOnline) - { - return new Status - { - Attitude = StatusAttitude.Neutral, - IsOnline = true, - IsCustomized = false, - Label = "Online", - AccountId = userId, - }; - } - - return new Status - { - Attitude = StatusAttitude.Neutral, - IsOnline = false, - IsCustomized = false, - Label = "Offline", - AccountId = userId, - }; - } - - public async Task> GetStatuses(List userIds) - { - var results = new Dictionary(); - var cacheMissUserIds = new List(); - - foreach (var userId in userIds) - { - var cacheKey = $"{StatusCacheKey}{userId}"; - var cachedStatus = await cache.GetAsync(cacheKey); - if (cachedStatus != null) - { - cachedStatus.IsOnline = !cachedStatus.IsInvisible && ws.GetAccountIsConnected(userId); - results[userId] = cachedStatus; - } - else - { - cacheMissUserIds.Add(userId); - } - } - - if (cacheMissUserIds.Any()) - { - var now = SystemClock.Instance.GetCurrentInstant(); - var statusesFromDb = await db.AccountStatuses - .Where(e => cacheMissUserIds.Contains(e.AccountId)) - .Where(e => e.ClearedAt == null || e.ClearedAt > now) - .GroupBy(e => e.AccountId) - .Select(g => g.OrderByDescending(e => e.CreatedAt).First()) - .ToListAsync(); - - var foundUserIds = new HashSet(); - - foreach (var status in statusesFromDb) - { - var isOnline = ws.GetAccountIsConnected(status.AccountId); - status.IsOnline = !status.IsInvisible && isOnline; - results[status.AccountId] = status; - var cacheKey = $"{StatusCacheKey}{status.AccountId}"; - await cache.SetAsync(cacheKey, status, TimeSpan.FromMinutes(5)); - foundUserIds.Add(status.AccountId); - } - - var usersWithoutStatus = cacheMissUserIds.Except(foundUserIds).ToList(); - if (usersWithoutStatus.Any()) - { - foreach (var userId in usersWithoutStatus) - { - var isOnline = ws.GetAccountIsConnected(userId); - var defaultStatus = new Status - { - Attitude = StatusAttitude.Neutral, - IsOnline = isOnline, - IsCustomized = false, - Label = isOnline ? "Online" : "Offline", - AccountId = userId, - }; - results[userId] = defaultStatus; - } - } - } - - return results; - } - - public async Task CreateStatus(Account user, Status status) - { - var now = SystemClock.Instance.GetCurrentInstant(); - await db.AccountStatuses - .Where(x => x.AccountId == user.Id && (x.ClearedAt == null || x.ClearedAt > now)) - .ExecuteUpdateAsync(s => s.SetProperty(x => x.ClearedAt, now)); - - db.AccountStatuses.Add(status); - await db.SaveChangesAsync(); - - return status; - } - - public async Task ClearStatus(Account user, Status status) - { - status.ClearedAt = SystemClock.Instance.GetCurrentInstant(); - db.Update(status); - await db.SaveChangesAsync(); - PurgeStatusCache(user.Id); - } - - private const int FortuneTipCount = 7; // This will be the max index for each type (positive/negative) - private const string CaptchaCacheKey = "CheckInCaptcha_"; - private const int CaptchaProbabilityPercent = 20; - - public async Task CheckInDailyDoAskCaptcha(Account user) - { - var cacheKey = $"{CaptchaCacheKey}{user.Id}"; - var needsCaptcha = await cache.GetAsync(cacheKey); - if (needsCaptcha is not null) - return needsCaptcha!.Value; - - var result = Random.Next(100) < CaptchaProbabilityPercent; - await cache.SetAsync(cacheKey, result, TimeSpan.FromHours(24)); - return result; - } - - public async Task CheckInDailyIsAvailable(Account user) - { - var now = SystemClock.Instance.GetCurrentInstant(); - var lastCheckIn = await db.AccountCheckInResults - .Where(x => x.AccountId == user.Id) - .OrderByDescending(x => x.CreatedAt) - .FirstOrDefaultAsync(); - - if (lastCheckIn == null) - return true; - - var lastDate = lastCheckIn.CreatedAt.InUtc().Date; - var currentDate = now.InUtc().Date; - - return lastDate < currentDate; - } - - public const string CheckInLockKey = "CheckInLock_"; - - public async Task CheckInDaily(Account user) - { - var lockKey = $"{CheckInLockKey}{user.Id}"; - - try - { - var lk = await cache.AcquireLockAsync(lockKey, TimeSpan.FromMinutes(1), TimeSpan.FromMilliseconds(100)); - - if (lk != null) - await lk.ReleaseAsync(); - } - catch - { - // Ignore errors from this pre-check - } - - // Now try to acquire the lock properly - await using var lockObj = await cache.AcquireLockAsync(lockKey, TimeSpan.FromMinutes(1), TimeSpan.FromSeconds(5)); - if (lockObj is null) throw new InvalidOperationException("Check-in was in progress."); - - var cultureInfo = new CultureInfo(user.Language, false); - CultureInfo.CurrentCulture = cultureInfo; - CultureInfo.CurrentUICulture = cultureInfo; - - // Generate 2 positive tips - var positiveIndices = Enumerable.Range(1, FortuneTipCount) - .OrderBy(_ => Random.Next()) - .Take(2) - .ToList(); - var tips = positiveIndices.Select(index => new FortuneTip - { - IsPositive = true, Title = localizer[$"FortuneTipPositiveTitle_{index}"].Value, - Content = localizer[$"FortuneTipPositiveContent_{index}"].Value - }).ToList(); - - // Generate 2 negative tips - var negativeIndices = Enumerable.Range(1, FortuneTipCount) - .Except(positiveIndices) - .OrderBy(_ => Random.Next()) - .Take(2) - .ToList(); - tips.AddRange(negativeIndices.Select(index => new FortuneTip - { - IsPositive = false, Title = localizer[$"FortuneTipNegativeTitle_{index}"].Value, - Content = localizer[$"FortuneTipNegativeContent_{index}"].Value - })); - - var result = new CheckInResult - { - Tips = tips, - Level = (CheckInResultLevel)Random.Next(Enum.GetValues().Length), - AccountId = user.Id, - RewardExperience = 100, - RewardPoints = 10, - }; - - var now = SystemClock.Instance.GetCurrentInstant().InUtc().Date; - try - { - if (result.RewardPoints.HasValue) - await payment.CreateTransactionWithAccountAsync( - null, - user.Id, - WalletCurrency.SourcePoint, - result.RewardPoints.Value, - $"Check-in reward on {now:yyyy/MM/dd}" - ); - } - catch - { - result.RewardPoints = null; - } - - await db.AccountProfiles - .Where(p => p.AccountId == user.Id) - .ExecuteUpdateAsync(s => - s.SetProperty(b => b.Experience, b => b.Experience + result.RewardExperience) - ); - db.AccountCheckInResults.Add(result); - await db.SaveChangesAsync(); // Don't forget to save changes to the database - - // The lock will be automatically released by the await using statement - return result; - } - - public async Task> GetEventCalendar(Account user, int month, int year = 0, - bool replaceInvisible = false) - { - if (year == 0) - year = SystemClock.Instance.GetCurrentInstant().InUtc().Date.Year; - - // Create start and end dates for the specified month - var startOfMonth = new LocalDate(year, month, 1).AtStartOfDayInZone(DateTimeZone.Utc).ToInstant(); - var endOfMonth = startOfMonth.Plus(Duration.FromDays(DateTime.DaysInMonth(year, month))); - - var statuses = await db.AccountStatuses - .AsNoTracking() - .TagWith("GetEventCalendar_Statuses") - .Where(x => x.AccountId == user.Id && x.CreatedAt >= startOfMonth && x.CreatedAt < endOfMonth) - .Select(x => new Status - { - Id = x.Id, - Attitude = x.Attitude, - IsInvisible = !replaceInvisible && x.IsInvisible, - IsNotDisturb = x.IsNotDisturb, - Label = x.Label, - ClearedAt = x.ClearedAt, - AccountId = x.AccountId, - CreatedAt = x.CreatedAt - }) - .OrderBy(x => x.CreatedAt) - .ToListAsync(); - - var checkIn = await db.AccountCheckInResults - .AsNoTracking() - .TagWith("GetEventCalendar_CheckIn") - .Where(x => x.AccountId == user.Id && x.CreatedAt >= startOfMonth && x.CreatedAt < endOfMonth) - .ToListAsync(); - - var dates = Enumerable.Range(1, DateTime.DaysInMonth(year, month)) - .Select(day => new LocalDate(year, month, day).AtStartOfDayInZone(DateTimeZone.Utc).ToInstant()) - .ToList(); - - var statusesByDate = statuses - .GroupBy(s => s.CreatedAt.InUtc().Date) - .ToDictionary(g => g.Key, g => g.ToList()); - - var checkInByDate = checkIn - .ToDictionary(c => c.CreatedAt.InUtc().Date); - - return dates.Select(date => - { - var utcDate = date.InUtc().Date; - return new DailyEventResponse - { - Date = date, - CheckInResult = checkInByDate.GetValueOrDefault(utcDate), - Statuses = statusesByDate.GetValueOrDefault(utcDate, new List()) - }; - }).ToList(); - } -} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Account/AccountService.cs b/DysonNetwork.Sphere/Account/AccountService.cs deleted file mode 100644 index db3b9e8..0000000 --- a/DysonNetwork.Sphere/Account/AccountService.cs +++ /dev/null @@ -1,657 +0,0 @@ -using System.Globalization; -using DysonNetwork.Sphere.Auth; -using DysonNetwork.Sphere.Auth.OpenId; -using DysonNetwork.Sphere.Email; - -using DysonNetwork.Sphere.Localization; -using DysonNetwork.Sphere.Permission; -using DysonNetwork.Sphere.Storage; -using EFCore.BulkExtensions; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Localization; -using NodaTime; -using Org.BouncyCastle.Utilities; -using OtpNet; - -namespace DysonNetwork.Sphere.Account; - -public class AccountService( - AppDatabase db, - MagicSpellService spells, - AccountUsernameService uname, - NotificationService nty, - EmailService mailer, - IStringLocalizer localizer, - ICacheService cache, - ILogger logger -) -{ - public static void SetCultureInfo(Account account) - { - SetCultureInfo(account.Language); - } - - public static void SetCultureInfo(string? languageCode) - { - var info = new CultureInfo(languageCode ?? "en-us", false); - CultureInfo.CurrentCulture = info; - CultureInfo.CurrentUICulture = info; - } - - public const string AccountCachePrefix = "account:"; - - public async Task PurgeAccountCache(Account account) - { - await cache.RemoveGroupAsync($"{AccountCachePrefix}{account.Id}"); - } - - public async Task LookupAccount(string probe) - { - var account = await db.Accounts.Where(a => a.Name == probe).FirstOrDefaultAsync(); - if (account is not null) return account; - - var contact = await db.AccountContacts - .Where(c => c.Content == probe) - .Include(c => c.Account) - .FirstOrDefaultAsync(); - return contact?.Account; - } - - public async Task LookupAccountByConnection(string identifier, string provider) - { - var connection = await db.AccountConnections - .Where(c => c.ProvidedIdentifier == identifier && c.Provider == provider) - .Include(c => c.Account) - .FirstOrDefaultAsync(); - return connection?.Account; - } - - public async Task GetAccountLevel(Guid accountId) - { - var profile = await db.AccountProfiles - .Where(a => a.AccountId == accountId) - .FirstOrDefaultAsync(); - return profile?.Level; - } - - public async Task CreateAccount( - string name, - string nick, - string email, - string? password, - string language = "en-US", - bool isEmailVerified = false, - bool isActivated = false - ) - { - await using var transaction = await db.Database.BeginTransactionAsync(); - try - { - var dupeNameCount = await db.Accounts.Where(a => a.Name == name).CountAsync(); - if (dupeNameCount > 0) - throw new InvalidOperationException("Account name has already been taken."); - - var account = new Account - { - Name = name, - Nick = nick, - Language = language, - Contacts = new List - { - new() - { - Type = AccountContactType.Email, - Content = email, - VerifiedAt = isEmailVerified ? SystemClock.Instance.GetCurrentInstant() : null, - IsPrimary = true - } - }, - AuthFactors = password is not null - ? new List - { - new AccountAuthFactor - { - Type = AccountAuthFactorType.Password, - Secret = password, - EnabledAt = SystemClock.Instance.GetCurrentInstant() - }.HashSecret() - } - : [], - Profile = new Profile() - }; - - if (isActivated) - { - account.ActivatedAt = SystemClock.Instance.GetCurrentInstant(); - var defaultGroup = await db.PermissionGroups.FirstOrDefaultAsync(g => g.Key == "default"); - if (defaultGroup is not null) - { - db.PermissionGroupMembers.Add(new PermissionGroupMember - { - Actor = $"user:{account.Id}", - Group = defaultGroup - }); - } - } - else - { - var spell = await spells.CreateMagicSpell( - account, - MagicSpellType.AccountActivation, - new Dictionary - { - { "contact_method", account.Contacts.First().Content } - } - ); - await spells.NotifyMagicSpell(spell, true); - } - - db.Accounts.Add(account); - await db.SaveChangesAsync(); - - await transaction.CommitAsync(); - return account; - } - catch - { - await transaction.RollbackAsync(); - throw; - } - } - - public async Task CreateAccount(OidcUserInfo userInfo) - { - if (string.IsNullOrEmpty(userInfo.Email)) - throw new ArgumentException("Email is required for account creation"); - - var displayName = !string.IsNullOrEmpty(userInfo.DisplayName) - ? userInfo.DisplayName - : $"{userInfo.FirstName} {userInfo.LastName}".Trim(); - - // Generate username from email - var username = await uname.GenerateUsernameFromEmailAsync(userInfo.Email); - - return await CreateAccount( - username, - displayName, - userInfo.Email, - null, - "en-US", - userInfo.EmailVerified, - userInfo.EmailVerified - ); - } - - public async Task RequestAccountDeletion(Account account) - { - var spell = await spells.CreateMagicSpell( - account, - MagicSpellType.AccountRemoval, - new Dictionary(), - SystemClock.Instance.GetCurrentInstant().Plus(Duration.FromHours(24)), - preventRepeat: true - ); - await spells.NotifyMagicSpell(spell); - } - - public async Task RequestPasswordReset(Account account) - { - var spell = await spells.CreateMagicSpell( - account, - MagicSpellType.AuthPasswordReset, - new Dictionary(), - SystemClock.Instance.GetCurrentInstant().Plus(Duration.FromHours(24)), - preventRepeat: true - ); - await spells.NotifyMagicSpell(spell); - } - - public async Task CheckAuthFactorExists(Account account, AccountAuthFactorType type) - { - var isExists = await db.AccountAuthFactors - .Where(x => x.AccountId == account.Id && x.Type == type) - .AnyAsync(); - return isExists; - } - - public async Task CreateAuthFactor(Account account, AccountAuthFactorType type, string? secret) - { - AccountAuthFactor? factor = null; - switch (type) - { - case AccountAuthFactorType.Password: - if (string.IsNullOrWhiteSpace(secret)) throw new ArgumentNullException(nameof(secret)); - factor = new AccountAuthFactor - { - Type = AccountAuthFactorType.Password, - Trustworthy = 1, - AccountId = account.Id, - Secret = secret, - EnabledAt = SystemClock.Instance.GetCurrentInstant(), - }.HashSecret(); - break; - case AccountAuthFactorType.EmailCode: - factor = new AccountAuthFactor - { - Type = AccountAuthFactorType.EmailCode, - Trustworthy = 2, - EnabledAt = SystemClock.Instance.GetCurrentInstant(), - }; - break; - case AccountAuthFactorType.InAppCode: - factor = new AccountAuthFactor - { - Type = AccountAuthFactorType.InAppCode, - Trustworthy = 1, - EnabledAt = SystemClock.Instance.GetCurrentInstant() - }; - break; - case AccountAuthFactorType.TimedCode: - var skOtp = KeyGeneration.GenerateRandomKey(20); - var skOtp32 = Base32Encoding.ToString(skOtp); - factor = new AccountAuthFactor - { - Secret = skOtp32, - Type = AccountAuthFactorType.TimedCode, - Trustworthy = 2, - EnabledAt = null, // It needs to be tired once to enable - CreatedResponse = new Dictionary - { - ["uri"] = new OtpUri( - OtpType.Totp, - skOtp32, - account.Id.ToString(), - "Solar Network" - ).ToString(), - } - }; - break; - case AccountAuthFactorType.PinCode: - if (string.IsNullOrWhiteSpace(secret)) throw new ArgumentNullException(nameof(secret)); - if (!secret.All(char.IsDigit) || secret.Length != 6) - throw new ArgumentException("PIN code must be exactly 6 digits"); - factor = new AccountAuthFactor - { - Type = AccountAuthFactorType.PinCode, - Trustworthy = 0, // Only for confirming, can't be used for login - Secret = secret, - EnabledAt = SystemClock.Instance.GetCurrentInstant(), - }.HashSecret(); - break; - default: - throw new ArgumentOutOfRangeException(nameof(type), type, null); - } - - if (factor is null) throw new InvalidOperationException("Unable to create auth factor."); - factor.AccountId = account.Id; - db.AccountAuthFactors.Add(factor); - await db.SaveChangesAsync(); - return factor; - } - - public async Task EnableAuthFactor(AccountAuthFactor factor, string? code) - { - if (factor.EnabledAt is not null) throw new ArgumentException("The factor has been enabled."); - if (factor.Type is AccountAuthFactorType.Password or AccountAuthFactorType.TimedCode) - { - if (code is null || !factor.VerifyPassword(code)) - throw new InvalidOperationException( - "Invalid code, you need to enter the correct code to enable the factor." - ); - } - - factor.EnabledAt = SystemClock.Instance.GetCurrentInstant(); - db.Update(factor); - await db.SaveChangesAsync(); - - return factor; - } - - public async Task DisableAuthFactor(AccountAuthFactor factor) - { - if (factor.EnabledAt is null) throw new ArgumentException("The factor has been disabled."); - - var count = await db.AccountAuthFactors - .Where(f => f.AccountId == factor.AccountId && f.EnabledAt != null) - .CountAsync(); - if (count <= 1) - throw new InvalidOperationException( - "Disabling this auth factor will cause you have no active auth factors."); - - factor.EnabledAt = null; - db.Update(factor); - await db.SaveChangesAsync(); - - return factor; - } - - public async Task DeleteAuthFactor(AccountAuthFactor factor) - { - var count = await db.AccountAuthFactors - .Where(f => f.AccountId == factor.AccountId) - .If(factor.EnabledAt is not null, q => q.Where(f => f.EnabledAt != null)) - .CountAsync(); - if (count <= 1) - throw new InvalidOperationException("Deleting this auth factor will cause you have no auth factor."); - - db.AccountAuthFactors.Remove(factor); - await db.SaveChangesAsync(); - } - - /// - /// Send the auth factor verification code to users, for factors like in-app code and email. - /// Sometimes it requires a hint, like a part of the user's email address to ensure the user is who own the account. - /// - /// The owner of the auth factor - /// The auth factor needed to send code - /// The part of the contact method for verification - public async Task SendFactorCode(Account account, AccountAuthFactor factor, string? hint = null) - { - var code = new Random().Next(100000, 999999).ToString("000000"); - - switch (factor.Type) - { - case AccountAuthFactorType.InAppCode: - if (await _GetFactorCode(factor) is not null) - throw new InvalidOperationException("A factor code has been sent and in active duration."); - - await nty.SendNotification( - account, - "auth.verification", - localizer["AuthCodeTitle"], - null, - localizer["AuthCodeBody", code], - save: true - ); - await _SetFactorCode(factor, code, TimeSpan.FromMinutes(5)); - break; - case AccountAuthFactorType.EmailCode: - if (await _GetFactorCode(factor) is not null) - throw new InvalidOperationException("A factor code has been sent and in active duration."); - - ArgumentNullException.ThrowIfNull(hint); - hint = hint.Replace("@", "").Replace(".", "").Replace("+", "").Replace("%", ""); - if (string.IsNullOrWhiteSpace(hint)) - { - logger.LogWarning( - "Unable to send factor code to #{FactorId} with hint {Hint}, due to invalid hint...", - factor.Id, - hint - ); - return; - } - - var contact = await db.AccountContacts - .Where(c => c.Type == AccountContactType.Email) - .Where(c => c.VerifiedAt != null) - .Where(c => EF.Functions.ILike(c.Content, $"%{hint}%")) - .Include(c => c.Account) - .FirstOrDefaultAsync(); - if (contact is null) - { - logger.LogWarning( - "Unable to send factor code to #{FactorId} with hint {Hint}, due to no contact method found according to hint...", - factor.Id, - hint - ); - return; - } - - await mailer.SendTemplatedEmailAsync( - account.Nick, - contact.Content, - localizer["VerificationEmail"], - new VerificationEmailModel - { - Name = account.Name, - Code = code - } - ); - - await _SetFactorCode(factor, code, TimeSpan.FromMinutes(30)); - break; - case AccountAuthFactorType.Password: - case AccountAuthFactorType.TimedCode: - default: - // No need to send, such as password etc... - return; - } - } - - public async Task VerifyFactorCode(AccountAuthFactor factor, string code) - { - switch (factor.Type) - { - case AccountAuthFactorType.EmailCode: - case AccountAuthFactorType.InAppCode: - var correctCode = await _GetFactorCode(factor); - var isCorrect = correctCode is not null && - string.Equals(correctCode, code, StringComparison.OrdinalIgnoreCase); - await cache.RemoveAsync($"{AuthFactorCachePrefix}{factor.Id}:code"); - return isCorrect; - case AccountAuthFactorType.Password: - case AccountAuthFactorType.TimedCode: - default: - return factor.VerifyPassword(code); - } - } - - private const string AuthFactorCachePrefix = "authfactor:"; - - private async Task _SetFactorCode(AccountAuthFactor factor, string code, TimeSpan expires) - { - await cache.SetAsync( - $"{AuthFactorCachePrefix}{factor.Id}:code", - code, - expires - ); - } - - private async Task _GetFactorCode(AccountAuthFactor factor) - { - return await cache.GetAsync( - $"{AuthFactorCachePrefix}{factor.Id}:code" - ); - } - - public async Task UpdateSessionLabel(Account account, Guid sessionId, string label) - { - var session = await db.AuthSessions - .Include(s => s.Challenge) - .Where(s => s.Id == sessionId && s.AccountId == account.Id) - .FirstOrDefaultAsync(); - if (session is null) throw new InvalidOperationException("Session was not found."); - - await db.AuthSessions - .Include(s => s.Challenge) - .Where(s => s.Challenge.DeviceId == session.Challenge.DeviceId) - .ExecuteUpdateAsync(p => p.SetProperty(s => s.Label, label)); - - var sessions = await db.AuthSessions - .Include(s => s.Challenge) - .Where(s => s.AccountId == session.Id && s.Challenge.DeviceId == session.Challenge.DeviceId) - .ToListAsync(); - foreach (var item in sessions) - await cache.RemoveAsync($"{DysonTokenAuthHandler.AuthCachePrefix}{item.Id}"); - - return session; - } - - public async Task DeleteSession(Account account, Guid sessionId) - { - var session = await db.AuthSessions - .Include(s => s.Challenge) - .Where(s => s.Id == sessionId && s.AccountId == account.Id) - .FirstOrDefaultAsync(); - if (session is null) throw new InvalidOperationException("Session was not found."); - - var sessions = await db.AuthSessions - .Include(s => s.Challenge) - .Where(s => s.AccountId == session.Id && s.Challenge.DeviceId == session.Challenge.DeviceId) - .ToListAsync(); - - if (session.Challenge.DeviceId is not null) - await nty.UnsubscribePushNotifications(session.Challenge.DeviceId); - - // The current session should be included in the sessions' list - await db.AuthSessions - .Include(s => s.Challenge) - .Where(s => s.Challenge.DeviceId == session.Challenge.DeviceId) - .ExecuteDeleteAsync(); - - foreach (var item in sessions) - await cache.RemoveAsync($"{DysonTokenAuthHandler.AuthCachePrefix}{item.Id}"); - } - - public async Task CreateContactMethod(Account account, AccountContactType type, string content) - { - var contact = new AccountContact - { - Type = type, - Content = content, - AccountId = account.Id, - }; - - db.AccountContacts.Add(contact); - await db.SaveChangesAsync(); - - return contact; - } - - public async Task VerifyContactMethod(Account account, AccountContact contact) - { - var spell = await spells.CreateMagicSpell( - account, - MagicSpellType.ContactVerification, - new Dictionary { { "contact_method", contact.Content } }, - expiredAt: SystemClock.Instance.GetCurrentInstant().Plus(Duration.FromHours(24)), - preventRepeat: true - ); - await spells.NotifyMagicSpell(spell); - } - - public async Task SetContactMethodPrimary(Account account, AccountContact contact) - { - if (contact.AccountId != account.Id) - throw new InvalidOperationException("Contact method does not belong to this account."); - if (contact.VerifiedAt is null) - throw new InvalidOperationException("Cannot set unverified contact method as primary."); - - await using var transaction = await db.Database.BeginTransactionAsync(); - - try - { - await db.AccountContacts - .Where(c => c.AccountId == account.Id && c.Type == contact.Type) - .ExecuteUpdateAsync(s => s.SetProperty(x => x.IsPrimary, false)); - - contact.IsPrimary = true; - db.AccountContacts.Update(contact); - await db.SaveChangesAsync(); - - await transaction.CommitAsync(); - return contact; - } - catch - { - await transaction.RollbackAsync(); - throw; - } - } - - public async Task DeleteContactMethod(Account account, AccountContact contact) - { - if (contact.AccountId != account.Id) - throw new InvalidOperationException("Contact method does not belong to this account."); - if (contact.IsPrimary) - throw new InvalidOperationException("Cannot delete primary contact method."); - - db.AccountContacts.Remove(contact); - await db.SaveChangesAsync(); - } - - /// - /// This method will grant a badge to the account. - /// Shouldn't be exposed to normal user and the user itself. - /// - public async Task GrantBadge(Account account, Badge badge) - { - badge.AccountId = account.Id; - db.Badges.Add(badge); - await db.SaveChangesAsync(); - return badge; - } - - /// - /// This method will revoke a badge from the account. - /// Shouldn't be exposed to normal user and the user itself. - /// - public async Task RevokeBadge(Account account, Guid badgeId) - { - var badge = await db.Badges - .Where(b => b.AccountId == account.Id && b.Id == badgeId) - .OrderByDescending(b => b.CreatedAt) - .FirstOrDefaultAsync(); - if (badge is null) throw new InvalidOperationException("Badge was not found."); - - var profile = await db.AccountProfiles - .Where(p => p.AccountId == account.Id) - .FirstOrDefaultAsync(); - if (profile?.ActiveBadge is not null && profile.ActiveBadge.Id == badge.Id) - profile.ActiveBadge = null; - - db.Remove(badge); - await db.SaveChangesAsync(); - } - - public async Task ActiveBadge(Account account, Guid badgeId) - { - await using var transaction = await db.Database.BeginTransactionAsync(); - - try - { - var badge = await db.Badges - .Where(b => b.AccountId == account.Id && b.Id == badgeId) - .OrderByDescending(b => b.CreatedAt) - .FirstOrDefaultAsync(); - if (badge is null) throw new InvalidOperationException("Badge was not found."); - - await db.Badges - .Where(b => b.AccountId == account.Id && b.Id != badgeId) - .ExecuteUpdateAsync(s => s.SetProperty(p => p.ActivatedAt, p => null)); - - badge.ActivatedAt = SystemClock.Instance.GetCurrentInstant(); - db.Update(badge); - await db.SaveChangesAsync(); - - await db.AccountProfiles - .Where(p => p.AccountId == account.Id) - .ExecuteUpdateAsync(s => s.SetProperty(p => p.ActiveBadge, badge.ToReference())); - await PurgeAccountCache(account); - - await transaction.CommitAsync(); - } - catch - { - await transaction.RollbackAsync(); - throw; - } - } - - /// - /// The maintenance method for server administrator. - /// To check every user has an account profile and to create them if it isn't having one. - /// - public async Task EnsureAccountProfileCreated() - { - var accountsId = await db.Accounts.Select(a => a.Id).ToListAsync(); - var existingId = await db.AccountProfiles.Select(p => p.AccountId).ToListAsync(); - var missingId = accountsId.Except(existingId).ToList(); - - if (missingId.Count != 0) - { - var newProfiles = missingId.Select(id => new Profile { Id = Guid.NewGuid(), AccountId = id }).ToList(); - await db.BulkInsertAsync(newProfiles); - } - } -} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Account/AccountUsernameService.cs b/DysonNetwork.Sphere/Account/AccountUsernameService.cs deleted file mode 100644 index 094f9f0..0000000 --- a/DysonNetwork.Sphere/Account/AccountUsernameService.cs +++ /dev/null @@ -1,105 +0,0 @@ -using System.Text.RegularExpressions; -using Microsoft.EntityFrameworkCore; - -namespace DysonNetwork.Sphere.Account; - -/// -/// Service for handling username generation and validation -/// -public class AccountUsernameService(AppDatabase db) -{ - private readonly Random _random = new(); - - /// - /// Generates a unique username based on the provided base name - /// - /// The preferred username - /// A unique username - public async Task GenerateUniqueUsernameAsync(string baseName) - { - // Sanitize the base name - var sanitized = SanitizeUsername(baseName); - - // If the base name is empty after sanitization, use a default - if (string.IsNullOrEmpty(sanitized)) - { - sanitized = "user"; - } - - // Check if the sanitized name is available - if (!await IsUsernameExistsAsync(sanitized)) - { - return sanitized; - } - - // Try up to 10 times with random numbers - for (int i = 0; i < 10; i++) - { - var suffix = _random.Next(1000, 9999); - var candidate = $"{sanitized}{suffix}"; - - if (!await IsUsernameExistsAsync(candidate)) - { - return candidate; - } - } - - // If all attempts fail, use a timestamp - var timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); - return $"{sanitized}{timestamp}"; - } - - /// - /// Sanitizes a username by removing invalid characters and converting to lowercase - /// - public string SanitizeUsername(string username) - { - if (string.IsNullOrEmpty(username)) - return string.Empty; - - // Replace spaces and special characters with underscores - var sanitized = Regex.Replace(username, @"[^a-zA-Z0-9_\-]", ""); - - // Convert to lowercase - sanitized = sanitized.ToLowerInvariant(); - - // Ensure it starts with a letter - if (sanitized.Length > 0 && !char.IsLetter(sanitized[0])) - { - sanitized = "u" + sanitized; - } - - // Truncate if too long - if (sanitized.Length > 30) - { - sanitized = sanitized[..30]; - } - - return sanitized; - } - - /// - /// Checks if a username already exists - /// - public async Task IsUsernameExistsAsync(string username) - { - return await db.Accounts.AnyAsync(a => a.Name == username); - } - - /// - /// Generates a username from an email address - /// - /// The email address to generate a username from - /// A unique username derived from the email - public async Task GenerateUsernameFromEmailAsync(string email) - { - if (string.IsNullOrEmpty(email)) - return await GenerateUniqueUsernameAsync("user"); - - // Extract the local part of the email (before the @) - var localPart = email.Split('@')[0]; - - // Use the local part as the base for username generation - return await GenerateUniqueUsernameAsync(localPart); - } -} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Account/ActionLog.cs b/DysonNetwork.Sphere/Account/ActionLog.cs deleted file mode 100644 index 19c5779..0000000 --- a/DysonNetwork.Sphere/Account/ActionLog.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; -using Point = NetTopologySuite.Geometries.Point; - -namespace DysonNetwork.Sphere.Account; - -public abstract class ActionLogType -{ - public const string NewLogin = "login"; - public const string ChallengeAttempt = "challenges.attempt"; - public const string ChallengeSuccess = "challenges.success"; - public const string ChallengeFailure = "challenges.failure"; - public const string PostCreate = "posts.create"; - public const string PostUpdate = "posts.update"; - public const string PostDelete = "posts.delete"; - public const string PostReact = "posts.react"; - public const string MessageCreate = "messages.create"; - public const string MessageUpdate = "messages.update"; - public const string MessageDelete = "messages.delete"; - public const string MessageReact = "messages.react"; - public const string PublisherCreate = "publishers.create"; - public const string PublisherUpdate = "publishers.update"; - public const string PublisherDelete = "publishers.delete"; - public const string PublisherMemberInvite = "publishers.members.invite"; - public const string PublisherMemberJoin = "publishers.members.join"; - public const string PublisherMemberLeave = "publishers.members.leave"; - public const string PublisherMemberKick = "publishers.members.kick"; - public const string RealmCreate = "realms.create"; - public const string RealmUpdate = "realms.update"; - public const string RealmDelete = "realms.delete"; - public const string RealmInvite = "realms.invite"; - public const string RealmJoin = "realms.join"; - public const string RealmLeave = "realms.leave"; - public const string RealmKick = "realms.kick"; - public const string RealmAdjustRole = "realms.role.edit"; - public const string ChatroomCreate = "chatrooms.create"; - public const string ChatroomUpdate = "chatrooms.update"; - public const string ChatroomDelete = "chatrooms.delete"; - public const string ChatroomInvite = "chatrooms.invite"; - public const string ChatroomJoin = "chatrooms.join"; - public const string ChatroomLeave = "chatrooms.leave"; - public const string ChatroomKick = "chatrooms.kick"; - public const string ChatroomAdjustRole = "chatrooms.role.edit"; -} - -public class ActionLog : ModelBase -{ - public Guid Id { get; set; } = Guid.NewGuid(); - [MaxLength(4096)] public string Action { get; set; } = null!; - [Column(TypeName = "jsonb")] public Dictionary Meta { get; set; } = new(); - [MaxLength(512)] public string? UserAgent { get; set; } - [MaxLength(128)] public string? IpAddress { get; set; } - public Point? Location { get; set; } - - public Guid AccountId { get; set; } - public Account Account { get; set; } = null!; - public Guid? SessionId { get; set; } -} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Account/ActionLogService.cs b/DysonNetwork.Sphere/Account/ActionLogService.cs deleted file mode 100644 index 0b963f2..0000000 --- a/DysonNetwork.Sphere/Account/ActionLogService.cs +++ /dev/null @@ -1,46 +0,0 @@ -using Quartz; -using DysonNetwork.Sphere.Connection; -using DysonNetwork.Sphere.Storage; -using DysonNetwork.Sphere.Storage.Handlers; - -namespace DysonNetwork.Sphere.Account; - -public class ActionLogService(GeoIpService geo, FlushBufferService fbs) -{ - public void CreateActionLog(Guid accountId, string action, Dictionary meta) - { - var log = new ActionLog - { - Action = action, - AccountId = accountId, - Meta = meta, - }; - - fbs.Enqueue(log); - } - - public void CreateActionLogFromRequest(string action, Dictionary meta, HttpRequest request, - Account? account = null) - { - var log = new ActionLog - { - Action = action, - Meta = meta, - UserAgent = request.Headers.UserAgent, - IpAddress = request.HttpContext.Connection.RemoteIpAddress?.ToString(), - Location = geo.GetPointFromIp(request.HttpContext.Connection.RemoteIpAddress?.ToString()) - }; - - if (request.HttpContext.Items["CurrentUser"] is Account currentUser) - log.AccountId = currentUser.Id; - else if (account != null) - log.AccountId = account.Id; - else - throw new ArgumentException("No user context was found"); - - if (request.HttpContext.Items["CurrentSession"] is Auth.Session currentSession) - log.SessionId = currentSession.Id; - - fbs.Enqueue(log); - } -} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Account/Badge.cs b/DysonNetwork.Sphere/Account/Badge.cs deleted file mode 100644 index 11368c4..0000000 --- a/DysonNetwork.Sphere/Account/Badge.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; -using System.Text.Json.Serialization; -using NodaTime; - -namespace DysonNetwork.Sphere.Account; - -public class Badge : ModelBase -{ - public Guid Id { get; set; } = Guid.NewGuid(); - [MaxLength(1024)] public string Type { get; set; } = null!; - [MaxLength(1024)] public string? Label { get; set; } - [MaxLength(4096)] public string? Caption { get; set; } - [Column(TypeName = "jsonb")] public Dictionary Meta { get; set; } = new(); - public Instant? ActivatedAt { get; set; } - public Instant? ExpiredAt { get; set; } - - public Guid AccountId { get; set; } - [JsonIgnore] public Account Account { get; set; } = null!; - - public BadgeReferenceObject ToReference() - { - return new BadgeReferenceObject - { - Id = Id, - Type = Type, - Label = Label, - Caption = Caption, - Meta = Meta, - ActivatedAt = ActivatedAt, - ExpiredAt = ExpiredAt, - AccountId = AccountId - }; - } -} - -public class BadgeReferenceObject : ModelBase -{ - public Guid Id { get; set; } - public string Type { get; set; } = null!; - public string? Label { get; set; } - public string? Caption { get; set; } - public Dictionary? Meta { get; set; } - public Instant? ActivatedAt { get; set; } - public Instant? ExpiredAt { get; set; } - public Guid AccountId { get; set; } -} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Account/Event.cs b/DysonNetwork.Sphere/Account/Event.cs deleted file mode 100644 index 57fca37..0000000 --- a/DysonNetwork.Sphere/Account/Event.cs +++ /dev/null @@ -1,65 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; -using NodaTime; - -namespace DysonNetwork.Sphere.Account; - -public enum StatusAttitude -{ - Positive, - Negative, - Neutral -} - -public class Status : ModelBase -{ - public Guid Id { get; set; } = Guid.NewGuid(); - public StatusAttitude Attitude { get; set; } - [NotMapped] public bool IsOnline { get; set; } - [NotMapped] public bool IsCustomized { get; set; } = true; - public bool IsInvisible { get; set; } - public bool IsNotDisturb { get; set; } - [MaxLength(1024)] public string? Label { get; set; } - public Instant? ClearedAt { get; set; } - - public Guid AccountId { get; set; } - public Account Account { get; set; } = null!; -} - -public enum CheckInResultLevel -{ - Worst, - Worse, - Normal, - Better, - Best -} - -public class CheckInResult : ModelBase -{ - public Guid Id { get; set; } = Guid.NewGuid(); - public CheckInResultLevel Level { get; set; } - public decimal? RewardPoints { get; set; } - public int? RewardExperience { get; set; } - [Column(TypeName = "jsonb")] public ICollection Tips { get; set; } = new List(); - - public Guid AccountId { get; set; } - public Account Account { get; set; } = null!; -} - -public class FortuneTip -{ - public bool IsPositive { get; set; } - public string Title { get; set; } = null!; - public string Content { get; set; } = null!; -} - -/// -/// This method should not be mapped. Used to generate the daily event calendar. -/// -public class DailyEventResponse -{ - public Instant Date { get; set; } - public CheckInResult? CheckInResult { get; set; } - public ICollection Statuses { get; set; } = new List(); -} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Account/MagicSpell.cs b/DysonNetwork.Sphere/Account/MagicSpell.cs deleted file mode 100644 index 674809c..0000000 --- a/DysonNetwork.Sphere/Account/MagicSpell.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; -using System.Text.Json.Serialization; -using Microsoft.EntityFrameworkCore; -using NodaTime; - -namespace DysonNetwork.Sphere.Account; - -public enum MagicSpellType -{ - AccountActivation, - AccountDeactivation, - AccountRemoval, - AuthPasswordReset, - ContactVerification, -} - -[Index(nameof(Spell), IsUnique = true)] -public class MagicSpell : ModelBase -{ - public Guid Id { get; set; } = Guid.NewGuid(); - [JsonIgnore] [MaxLength(1024)] public string Spell { get; set; } = null!; - public MagicSpellType Type { get; set; } - public Instant? ExpiresAt { get; set; } - public Instant? AffectedAt { get; set; } - [Column(TypeName = "jsonb")] public Dictionary Meta { get; set; } = new(); - - public Guid? AccountId { get; set; } - public Account? Account { get; set; } -} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Account/MagicSpellController.cs b/DysonNetwork.Sphere/Account/MagicSpellController.cs deleted file mode 100644 index a98f882..0000000 --- a/DysonNetwork.Sphere/Account/MagicSpellController.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Microsoft.AspNetCore.Mvc; - -namespace DysonNetwork.Sphere.Account; - -[ApiController] -[Route("/api/spells")] -public class MagicSpellController(AppDatabase db, MagicSpellService sp) : ControllerBase -{ - [HttpPost("{spellId:guid}/resend")] - public async Task ResendMagicSpell(Guid spellId) - { - var spell = db.MagicSpells.FirstOrDefault(x => x.Id == spellId); - if (spell == null) - return NotFound(); - - await sp.NotifyMagicSpell(spell, true); - return Ok(); - } -} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Account/MagicSpellService.cs b/DysonNetwork.Sphere/Account/MagicSpellService.cs deleted file mode 100644 index 4cf2521..0000000 --- a/DysonNetwork.Sphere/Account/MagicSpellService.cs +++ /dev/null @@ -1,252 +0,0 @@ -using System.Globalization; -using System.Security.Cryptography; -using System.Text.Json; -using DysonNetwork.Sphere.Email; -using DysonNetwork.Sphere.Pages.Emails; -using DysonNetwork.Sphere.Permission; -using DysonNetwork.Sphere.Resources.Localization; -using DysonNetwork.Sphere.Resources.Pages.Emails; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Localization; -using NodaTime; - -namespace DysonNetwork.Sphere.Account; - -public class MagicSpellService( - AppDatabase db, - EmailService email, - IConfiguration configuration, - ILogger logger, - IStringLocalizer localizer -) -{ - public async Task CreateMagicSpell( - Account account, - MagicSpellType type, - Dictionary meta, - Instant? expiredAt = null, - Instant? affectedAt = null, - bool preventRepeat = false - ) - { - if (preventRepeat) - { - var now = SystemClock.Instance.GetCurrentInstant(); - var existingSpell = await db.MagicSpells - .Where(s => s.AccountId == account.Id) - .Where(s => s.Type == type) - .Where(s => s.ExpiresAt == null || s.ExpiresAt > now) - .FirstOrDefaultAsync(); - - if (existingSpell != null) - { - throw new InvalidOperationException($"Account already has an active magic spell of type {type}"); - } - } - - var spellWord = _GenerateRandomString(128); - var spell = new MagicSpell - { - Spell = spellWord, - Type = type, - ExpiresAt = expiredAt, - AffectedAt = affectedAt, - AccountId = account.Id, - Meta = meta - }; - - db.MagicSpells.Add(spell); - await db.SaveChangesAsync(); - - return spell; - } - - public async Task NotifyMagicSpell(MagicSpell spell, bool bypassVerify = false) - { - var contact = await db.AccountContacts - .Where(c => c.Account.Id == spell.AccountId) - .Where(c => c.Type == AccountContactType.Email) - .Where(c => c.VerifiedAt != null || bypassVerify) - .OrderByDescending(c => c.IsPrimary) - .Include(c => c.Account) - .FirstOrDefaultAsync(); - if (contact is null) throw new ArgumentException("Account has no contact method that can use"); - - var link = $"{configuration.GetValue("BaseUrl")}/spells/{Uri.EscapeDataString(spell.Spell)}"; - - logger.LogInformation("Sending magic spell... {Link}", link); - - var accountLanguage = await db.Accounts - .Where(a => a.Id == spell.AccountId) - .Select(a => a.Language) - .FirstOrDefaultAsync(); - AccountService.SetCultureInfo(accountLanguage); - - try - { - switch (spell.Type) - { - case MagicSpellType.AccountActivation: - await email.SendTemplatedEmailAsync( - contact.Account.Nick, - contact.Content, - localizer["EmailLandingTitle"], - new LandingEmailModel - { - Name = contact.Account.Name, - Link = link - } - ); - break; - case MagicSpellType.AccountRemoval: - await email.SendTemplatedEmailAsync( - contact.Account.Nick, - contact.Content, - localizer["EmailAccountDeletionTitle"], - new AccountDeletionEmailModel - { - Name = contact.Account.Name, - Link = link - } - ); - break; - case MagicSpellType.AuthPasswordReset: - await email.SendTemplatedEmailAsync( - contact.Account.Nick, - contact.Content, - localizer["EmailAccountDeletionTitle"], - new PasswordResetEmailModel - { - Name = contact.Account.Name, - Link = link - } - ); - break; - case MagicSpellType.ContactVerification: - if (spell.Meta["contact_method"] is not string contactMethod) - throw new InvalidOperationException("Contact method is not found."); - await email.SendTemplatedEmailAsync( - contact.Account.Nick, - contactMethod!, - localizer["EmailContactVerificationTitle"], - new ContactVerificationEmailModel - { - Name = contact.Account.Name, - Link = link - } - ); - break; - default: - throw new ArgumentOutOfRangeException(); - } - } - catch (Exception err) - { - logger.LogError($"Error sending magic spell (${spell.Spell})... {err}"); - } - } - - public async Task ApplyMagicSpell(MagicSpell spell) - { - switch (spell.Type) - { - case MagicSpellType.AuthPasswordReset: - throw new ArgumentException( - "For password reset spell, please use the ApplyPasswordReset method instead." - ); - case MagicSpellType.AccountRemoval: - var account = await db.Accounts.FirstOrDefaultAsync(c => c.Id == spell.AccountId); - if (account is null) break; - db.Accounts.Remove(account); - break; - case MagicSpellType.AccountActivation: - var contactMethod = (spell.Meta["contact_method"] as JsonElement? ?? default).ToString(); - var contact = await - db.AccountContacts.FirstOrDefaultAsync(c => - c.Content == contactMethod - ); - if (contact is not null) - { - contact.VerifiedAt = SystemClock.Instance.GetCurrentInstant(); - db.Update(contact); - } - - account = await db.Accounts.FirstOrDefaultAsync(c => c.Id == spell.AccountId); - if (account is not null) - { - account.ActivatedAt = SystemClock.Instance.GetCurrentInstant(); - db.Update(account); - } - - var defaultGroup = await db.PermissionGroups.FirstOrDefaultAsync(g => g.Key == "default"); - if (defaultGroup is not null && account is not null) - { - db.PermissionGroupMembers.Add(new PermissionGroupMember - { - Actor = $"user:{account.Id}", - Group = defaultGroup - }); - } - - break; - case MagicSpellType.ContactVerification: - var verifyContactMethod = (spell.Meta["contact_method"] as JsonElement? ?? default).ToString(); - var verifyContact = await db.AccountContacts - .FirstOrDefaultAsync(c => c.Content == verifyContactMethod); - if (verifyContact is not null) - { - verifyContact.VerifiedAt = SystemClock.Instance.GetCurrentInstant(); - db.Update(verifyContact); - } - - break; - default: - throw new ArgumentOutOfRangeException(); - } - - db.Remove(spell); - await db.SaveChangesAsync(); - } - - public async Task ApplyPasswordReset(MagicSpell spell, string newPassword) - { - if (spell.Type != MagicSpellType.AuthPasswordReset) - throw new ArgumentException("This spell is not a password reset spell."); - - var passwordFactor = await db.AccountAuthFactors - .Include(f => f.Account) - .Where(f => f.Type == AccountAuthFactorType.Password && f.Account.Id == spell.AccountId) - .FirstOrDefaultAsync(); - if (passwordFactor is null) - { - var account = await db.Accounts.FirstOrDefaultAsync(c => c.Id == spell.AccountId); - if (account is null) throw new InvalidOperationException("Both account and auth factor was not found."); - passwordFactor = new AccountAuthFactor - { - Type = AccountAuthFactorType.Password, - Account = account, - Secret = newPassword - }.HashSecret(); - db.AccountAuthFactors.Add(passwordFactor); - } - else - { - passwordFactor.Secret = newPassword; - passwordFactor.HashSecret(); - db.Update(passwordFactor); - } - - await db.SaveChangesAsync(); - } - - private static string _GenerateRandomString(int length) - { - using var rng = RandomNumberGenerator.Create(); - var randomBytes = new byte[length]; - rng.GetBytes(randomBytes); - - var base64String = Convert.ToBase64String(randomBytes); - - return base64String.Substring(0, length); - } -} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Account/Notification.cs b/DysonNetwork.Sphere/Account/Notification.cs deleted file mode 100644 index 5095f83..0000000 --- a/DysonNetwork.Sphere/Account/Notification.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; -using System.Text.Json.Serialization; -using Microsoft.EntityFrameworkCore; -using NodaTime; - -namespace DysonNetwork.Sphere.Account; - -public class Notification : ModelBase -{ - public Guid Id { get; set; } = Guid.NewGuid(); - [MaxLength(1024)] public string Topic { get; set; } = null!; - [MaxLength(1024)] public string? Title { get; set; } - [MaxLength(2048)] public string? Subtitle { get; set; } - [MaxLength(4096)] public string? Content { get; set; } - [Column(TypeName = "jsonb")] public Dictionary? Meta { get; set; } - public int Priority { get; set; } = 10; - public Instant? ViewedAt { get; set; } - - public Guid AccountId { get; set; } - [JsonIgnore] public Account Account { get; set; } = null!; -} - -public enum NotificationPushProvider -{ - Apple, - Google -} - -[Index(nameof(DeviceToken), nameof(DeviceId), nameof(AccountId), IsUnique = true)] -public class NotificationPushSubscription : ModelBase -{ - public Guid Id { get; set; } = Guid.NewGuid(); - [MaxLength(4096)] public string DeviceId { get; set; } = null!; - [MaxLength(4096)] public string DeviceToken { get; set; } = null!; - public NotificationPushProvider Provider { get; set; } - public Instant? LastUsedAt { get; set; } - - public Guid AccountId { get; set; } - [JsonIgnore] public Account Account { get; set; } = null!; -} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Account/NotificationController.cs b/DysonNetwork.Sphere/Account/NotificationController.cs deleted file mode 100644 index 01d9a58..0000000 --- a/DysonNetwork.Sphere/Account/NotificationController.cs +++ /dev/null @@ -1,166 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using DysonNetwork.Sphere.Auth; -using DysonNetwork.Sphere.Permission; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; -using NodaTime; - -namespace DysonNetwork.Sphere.Account; - -[ApiController] -[Route("/api/notifications")] -public class NotificationController(AppDatabase db, NotificationService nty) : ControllerBase -{ - [HttpGet("count")] - [Authorize] - public async Task> CountUnreadNotifications() - { - HttpContext.Items.TryGetValue("CurrentUser", out var currentUserValue); - if (currentUserValue is not Account currentUser) return Unauthorized(); - - var count = await db.Notifications - .Where(s => s.AccountId == currentUser.Id && s.ViewedAt == null) - .CountAsync(); - return Ok(count); - } - - [HttpGet] - [Authorize] - public async Task>> ListNotifications( - [FromQuery] int offset = 0, - // The page size set to 5 is to avoid the client pulled the notification - // but didn't render it in the screen-viewable region. - [FromQuery] int take = 5 - ) - { - HttpContext.Items.TryGetValue("CurrentUser", out var currentUserValue); - if (currentUserValue is not Account currentUser) return Unauthorized(); - - var totalCount = await db.Notifications - .Where(s => s.AccountId == currentUser.Id) - .CountAsync(); - var notifications = await db.Notifications - .Where(s => s.AccountId == currentUser.Id) - .OrderByDescending(e => e.CreatedAt) - .Skip(offset) - .Take(take) - .ToListAsync(); - - Response.Headers["X-Total"] = totalCount.ToString(); - await nty.MarkNotificationsViewed(notifications); - - return Ok(notifications); - } - - public class PushNotificationSubscribeRequest - { - [MaxLength(4096)] public string DeviceToken { get; set; } = null!; - public NotificationPushProvider Provider { get; set; } - } - - [HttpPut("subscription")] - [Authorize] - public async Task> SubscribeToPushNotification( - [FromBody] PushNotificationSubscribeRequest request - ) - { - HttpContext.Items.TryGetValue("CurrentSession", out var currentSessionValue); - HttpContext.Items.TryGetValue("CurrentUser", out var currentUserValue); - var currentUser = currentUserValue as Account; - if (currentUser == null) return Unauthorized(); - var currentSession = currentSessionValue as Session; - if (currentSession == null) return Unauthorized(); - - var result = - await nty.SubscribePushNotification(currentUser, request.Provider, currentSession.Challenge.DeviceId!, - request.DeviceToken); - - return Ok(result); - } - - [HttpDelete("subscription")] - [Authorize] - public async Task> UnsubscribeFromPushNotification() - { - HttpContext.Items.TryGetValue("CurrentSession", out var currentSessionValue); - HttpContext.Items.TryGetValue("CurrentUser", out var currentUserValue); - var currentUser = currentUserValue as Account; - if (currentUser == null) return Unauthorized(); - var currentSession = currentSessionValue as Session; - if (currentSession == null) return Unauthorized(); - - var affectedRows = await db.NotificationPushSubscriptions - .Where(s => - s.AccountId == currentUser.Id && - s.DeviceId == currentSession.Challenge.DeviceId - ).ExecuteDeleteAsync(); - return Ok(affectedRows); - } - - public class NotificationRequest - { - [Required] [MaxLength(1024)] public string Topic { get; set; } = null!; - [Required] [MaxLength(1024)] public string Title { get; set; } = null!; - [MaxLength(2048)] public string? Subtitle { get; set; } - [Required] [MaxLength(4096)] public string Content { get; set; } = null!; - public Dictionary? Meta { get; set; } - public int Priority { get; set; } = 10; - } - - [HttpPost("broadcast")] - [Authorize] - [RequiredPermission("global", "notifications.broadcast")] - public async Task BroadcastNotification( - [FromBody] NotificationRequest request, - [FromQuery] bool save = false - ) - { - await nty.BroadcastNotification( - new Notification - { - CreatedAt = SystemClock.Instance.GetCurrentInstant(), - UpdatedAt = SystemClock.Instance.GetCurrentInstant(), - Topic = request.Topic, - Title = request.Title, - Subtitle = request.Subtitle, - Content = request.Content, - Meta = request.Meta, - Priority = request.Priority, - }, - save - ); - return Ok(); - } - - public class NotificationWithAimRequest : NotificationRequest - { - [Required] public List AccountId { get; set; } = null!; - } - - [HttpPost("send")] - [Authorize] - [RequiredPermission("global", "notifications.send")] - public async Task SendNotification( - [FromBody] NotificationWithAimRequest request, - [FromQuery] bool save = false - ) - { - var accounts = await db.Accounts.Where(a => request.AccountId.Contains(a.Id)).ToListAsync(); - await nty.SendNotificationBatch( - new Notification - { - CreatedAt = SystemClock.Instance.GetCurrentInstant(), - UpdatedAt = SystemClock.Instance.GetCurrentInstant(), - Topic = request.Topic, - Title = request.Title, - Subtitle = request.Subtitle, - Content = request.Content, - Meta = request.Meta, - }, - accounts, - save - ); - return Ok(); - } -} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Account/NotificationService.cs b/DysonNetwork.Sphere/Account/NotificationService.cs deleted file mode 100644 index 00ebe80..0000000 --- a/DysonNetwork.Sphere/Account/NotificationService.cs +++ /dev/null @@ -1,307 +0,0 @@ -using System.Text; -using System.Text.Json; -using DysonNetwork.Sphere.Connection; -using EFCore.BulkExtensions; -using Microsoft.EntityFrameworkCore; -using NodaTime; - -namespace DysonNetwork.Sphere.Account; - -public class NotificationService( - AppDatabase db, - WebSocketService ws, - IHttpClientFactory httpFactory, - IConfiguration config) -{ - private readonly string _notifyTopic = config["Notifications:Topic"]!; - private readonly Uri _notifyEndpoint = new(config["Notifications:Endpoint"]!); - - public async Task UnsubscribePushNotifications(string deviceId) - { - await db.NotificationPushSubscriptions - .Where(s => s.DeviceId == deviceId) - .ExecuteDeleteAsync(); - } - - public async Task SubscribePushNotification( - Account account, - NotificationPushProvider provider, - string deviceId, - string deviceToken - ) - { - var now = SystemClock.Instance.GetCurrentInstant(); - - // First check if a matching subscription exists - var existingSubscription = await db.NotificationPushSubscriptions - .Where(s => s.AccountId == account.Id) - .Where(s => s.DeviceId == deviceId || s.DeviceToken == deviceToken) - .FirstOrDefaultAsync(); - - if (existingSubscription is not null) - { - // Update the existing subscription directly in the database - await db.NotificationPushSubscriptions - .Where(s => s.Id == existingSubscription.Id) - .ExecuteUpdateAsync(setters => setters - .SetProperty(s => s.DeviceId, deviceId) - .SetProperty(s => s.DeviceToken, deviceToken) - .SetProperty(s => s.UpdatedAt, now)); - - // Return the updated subscription - existingSubscription.DeviceId = deviceId; - existingSubscription.DeviceToken = deviceToken; - existingSubscription.UpdatedAt = now; - return existingSubscription; - } - - var subscription = new NotificationPushSubscription - { - DeviceId = deviceId, - DeviceToken = deviceToken, - Provider = provider, - AccountId = account.Id, - }; - - db.NotificationPushSubscriptions.Add(subscription); - await db.SaveChangesAsync(); - - return subscription; - } - - public async Task SendNotification( - Account account, - string topic, - string? title = null, - string? subtitle = null, - string? content = null, - Dictionary? meta = null, - string? actionUri = null, - bool isSilent = false, - bool save = true - ) - { - if (title is null && subtitle is null && content is null) - throw new ArgumentException("Unable to send notification that completely empty."); - - meta ??= new Dictionary(); - if (actionUri is not null) meta["action_uri"] = actionUri; - - var notification = new Notification - { - Topic = topic, - Title = title, - Subtitle = subtitle, - Content = content, - Meta = meta, - AccountId = account.Id, - }; - - if (save) - { - db.Add(notification); - await db.SaveChangesAsync(); - } - - if (!isSilent) _ = DeliveryNotification(notification); - - return notification; - } - - public async Task DeliveryNotification(Notification notification) - { - ws.SendPacketToAccount(notification.AccountId, new WebSocketPacket - { - Type = "notifications.new", - Data = notification - }); - - // Pushing the notification - var subscribers = await db.NotificationPushSubscriptions - .Where(s => s.AccountId == notification.AccountId) - .ToListAsync(); - - await _PushNotification(notification, subscribers); - } - - public async Task MarkNotificationsViewed(ICollection notifications) - { - var now = SystemClock.Instance.GetCurrentInstant(); - var id = notifications.Where(n => n.ViewedAt == null).Select(n => n.Id).ToList(); - if (id.Count == 0) return; - - await db.Notifications - .Where(n => id.Contains(n.Id)) - .ExecuteUpdateAsync(s => s.SetProperty(n => n.ViewedAt, now) - ); - } - - public async Task BroadcastNotification(Notification notification, bool save = false) - { - var accounts = await db.Accounts.ToListAsync(); - - if (save) - { - var notifications = accounts.Select(x => - { - var newNotification = new Notification - { - Topic = notification.Topic, - Title = notification.Title, - Subtitle = notification.Subtitle, - Content = notification.Content, - Meta = notification.Meta, - Priority = notification.Priority, - Account = x, - AccountId = x.Id - }; - return newNotification; - }).ToList(); - await db.BulkInsertAsync(notifications); - } - - foreach (var account in accounts) - { - notification.Account = account; - notification.AccountId = account.Id; - ws.SendPacketToAccount(account.Id, new WebSocketPacket - { - Type = "notifications.new", - Data = notification - }); - } - - var subscribers = await db.NotificationPushSubscriptions - .ToListAsync(); - await _PushNotification(notification, subscribers); - } - - public async Task SendNotificationBatch(Notification notification, List accounts, bool save = false) - { - if (save) - { - var notifications = accounts.Select(x => - { - var newNotification = new Notification - { - Topic = notification.Topic, - Title = notification.Title, - Subtitle = notification.Subtitle, - Content = notification.Content, - Meta = notification.Meta, - Priority = notification.Priority, - AccountId = x.Id - }; - return newNotification; - }).ToList(); - await db.BulkInsertAsync(notifications); - } - - foreach (var account in accounts) - { - notification.Account = account; - notification.AccountId = account.Id; - ws.SendPacketToAccount(account.Id, new WebSocketPacket - { - Type = "notifications.new", - Data = notification - }); - } - - var accountsId = accounts.Select(x => x.Id).ToList(); - var subscribers = await db.NotificationPushSubscriptions - .Where(s => accountsId.Contains(s.AccountId)) - .ToListAsync(); - await _PushNotification(notification, subscribers); - } - - private List> _BuildNotificationPayload(Notification notification, - IEnumerable subscriptions) - { - var subDict = subscriptions - .GroupBy(x => x.Provider) - .ToDictionary(x => x.Key, x => x.ToList()); - - var notifications = subDict.Select(value => - { - var platformCode = value.Key switch - { - NotificationPushProvider.Apple => 1, - NotificationPushProvider.Google => 2, - _ => throw new InvalidOperationException($"Unknown push provider: {value.Key}") - }; - - var tokens = value.Value.Select(x => x.DeviceToken).ToList(); - return _BuildNotificationPayload(notification, platformCode, tokens); - }).ToList(); - - return notifications.ToList(); - } - - private Dictionary _BuildNotificationPayload(Notification notification, int platformCode, - IEnumerable deviceTokens) - { - var alertDict = new Dictionary(); - var dict = new Dictionary - { - ["notif_id"] = notification.Id.ToString(), - ["apns_id"] = notification.Id.ToString(), - ["topic"] = _notifyTopic, - ["tokens"] = deviceTokens, - ["data"] = new Dictionary - { - ["type"] = notification.Topic, - ["meta"] = notification.Meta ?? new Dictionary(), - }, - ["mutable_content"] = true, - ["priority"] = notification.Priority >= 5 ? "high" : "normal", - }; - - if (!string.IsNullOrWhiteSpace(notification.Title)) - { - dict["title"] = notification.Title; - alertDict["title"] = notification.Title; - } - - if (!string.IsNullOrWhiteSpace(notification.Content)) - { - dict["message"] = notification.Content; - alertDict["body"] = notification.Content; - } - - if (!string.IsNullOrWhiteSpace(notification.Subtitle)) - { - dict["message"] = $"{notification.Subtitle}\n{dict["message"]}"; - alertDict["subtitle"] = notification.Subtitle; - } - - if (notification.Priority >= 5) - dict["name"] = "default"; - - dict["platform"] = platformCode; - dict["alert"] = alertDict; - - return dict; - } - - private async Task _PushNotification(Notification notification, - IEnumerable subscriptions) - { - var subList = subscriptions.ToList(); - if (subList.Count == 0) return; - - var requestDict = new Dictionary - { - ["notifications"] = _BuildNotificationPayload(notification, subList) - }; - - var client = httpFactory.CreateClient(); - client.BaseAddress = _notifyEndpoint; - var request = await client.PostAsync("/push", new StringContent( - JsonSerializer.Serialize(requestDict), - Encoding.UTF8, - "application/json" - )); - request.EnsureSuccessStatusCode(); - } -} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Account/Relationship.cs b/DysonNetwork.Sphere/Account/Relationship.cs deleted file mode 100644 index b46b60a..0000000 --- a/DysonNetwork.Sphere/Account/Relationship.cs +++ /dev/null @@ -1,22 +0,0 @@ -using NodaTime; - -namespace DysonNetwork.Sphere.Account; - -public enum RelationshipStatus : short -{ - Friends = 100, - Pending = 0, - Blocked = -100 -} - -public class Relationship : ModelBase -{ - public Guid AccountId { get; set; } - public Account Account { get; set; } = null!; - public Guid RelatedId { get; set; } - public Account Related { get; set; } = null!; - - public Instant? ExpiredAt { get; set; } - - public RelationshipStatus Status { get; set; } = RelationshipStatus.Pending; -} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Account/RelationshipController.cs b/DysonNetwork.Sphere/Account/RelationshipController.cs deleted file mode 100644 index d91c43a..0000000 --- a/DysonNetwork.Sphere/Account/RelationshipController.cs +++ /dev/null @@ -1,253 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; -using NodaTime; - -namespace DysonNetwork.Sphere.Account; - -[ApiController] -[Route("/api/relationships")] -public class RelationshipController(AppDatabase db, RelationshipService rels) : ControllerBase -{ - [HttpGet] - [Authorize] - public async Task>> ListRelationships([FromQuery] int offset = 0, - [FromQuery] int take = 20) - { - if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); - var userId = currentUser.Id; - - var query = db.AccountRelationships.AsQueryable() - .Where(r => r.RelatedId == userId); - var totalCount = await query.CountAsync(); - var relationships = await query - .Include(r => r.Related) - .Include(r => r.Related.Profile) - .Include(r => r.Account) - .Include(r => r.Account.Profile) - .Skip(offset) - .Take(take) - .ToListAsync(); - - var statuses = await db.AccountRelationships - .Where(r => r.AccountId == userId) - .ToDictionaryAsync(r => r.RelatedId); - foreach (var relationship in relationships) - if (statuses.TryGetValue(relationship.RelatedId, out var status)) - relationship.Status = status.Status; - - Response.Headers["X-Total"] = totalCount.ToString(); - - return relationships; - } - - [HttpGet("requests")] - [Authorize] - public async Task>> ListSentRequests() - { - if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); - - var relationships = await db.AccountRelationships - .Where(r => r.AccountId == currentUser.Id && r.Status == RelationshipStatus.Pending) - .Include(r => r.Related) - .Include(r => r.Related.Profile) - .Include(r => r.Account) - .Include(r => r.Account.Profile) - .ToListAsync(); - - return relationships; - } - - public class RelationshipRequest - { - [Required] public RelationshipStatus Status { get; set; } - } - - [HttpPost("{userId:guid}")] - [Authorize] - public async Task> CreateRelationship(Guid userId, - [FromBody] RelationshipRequest request) - { - if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); - - var relatedUser = await db.Accounts.FindAsync(userId); - if (relatedUser is null) return NotFound("Account was not found."); - - try - { - var relationship = await rels.CreateRelationship( - currentUser, relatedUser, request.Status - ); - return relationship; - } - catch (InvalidOperationException err) - { - return BadRequest(err.Message); - } - } - - [HttpPatch("{userId:guid}")] - [Authorize] - public async Task> UpdateRelationship(Guid userId, - [FromBody] RelationshipRequest request) - { - if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); - - try - { - var relationship = await rels.UpdateRelationship(currentUser.Id, userId, request.Status); - return relationship; - } - catch (ArgumentException err) - { - return NotFound(err.Message); - } - catch (InvalidOperationException err) - { - return BadRequest(err.Message); - } - } - - [HttpGet("{userId:guid}")] - [Authorize] - public async Task> GetRelationship(Guid userId) - { - if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); - - var now = Instant.FromDateTimeUtc(DateTime.UtcNow); - var queries = db.AccountRelationships.AsQueryable() - .Where(r => r.AccountId == currentUser.Id && r.RelatedId == userId) - .Where(r => r.ExpiredAt == null || r.ExpiredAt > now); - var relationship = await queries - .Include(r => r.Related) - .Include(r => r.Related.Profile) - .FirstOrDefaultAsync(); - if (relationship is null) return NotFound(); - - relationship.Account = currentUser; - return Ok(relationship); - } - - [HttpPost("{userId:guid}/friends")] - [Authorize] - public async Task> SendFriendRequest(Guid userId) - { - if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); - - var relatedUser = await db.Accounts.FindAsync(userId); - if (relatedUser is null) return NotFound("Account was not found."); - - var existing = await db.AccountRelationships.FirstOrDefaultAsync(r => - (r.AccountId == currentUser.Id && r.RelatedId == userId) || - (r.AccountId == userId && r.RelatedId == currentUser.Id)); - if (existing != null) return BadRequest("Relationship already exists."); - - try - { - var relationship = await rels.SendFriendRequest(currentUser, relatedUser); - return relationship; - } - catch (InvalidOperationException err) - { - return BadRequest(err.Message); - } - } - - [HttpDelete("{userId:guid}/friends")] - [Authorize] - public async Task DeleteFriendRequest(Guid userId) - { - if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); - - try - { - await rels.DeleteFriendRequest(currentUser.Id, userId); - return NoContent(); - } - catch (ArgumentException err) - { - return NotFound(err.Message); - } - } - - [HttpPost("{userId:guid}/friends/accept")] - [Authorize] - public async Task> AcceptFriendRequest(Guid userId) - { - if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); - - var relationship = await rels.GetRelationship(userId, currentUser.Id, RelationshipStatus.Pending); - if (relationship is null) return NotFound("Friend request was not found."); - - try - { - relationship = await rels.AcceptFriendRelationship(relationship); - return relationship; - } - catch (InvalidOperationException err) - { - return BadRequest(err.Message); - } - } - - [HttpPost("{userId:guid}/friends/decline")] - [Authorize] - public async Task> DeclineFriendRequest(Guid userId) - { - if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); - - var relationship = await rels.GetRelationship(userId, currentUser.Id, RelationshipStatus.Pending); - if (relationship is null) return NotFound("Friend request was not found."); - - try - { - relationship = await rels.AcceptFriendRelationship(relationship, status: RelationshipStatus.Blocked); - return relationship; - } - catch (InvalidOperationException err) - { - return BadRequest(err.Message); - } - } - - [HttpPost("{userId:guid}/block")] - [Authorize] - public async Task> BlockUser(Guid userId) - { - if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); - - var relatedUser = await db.Accounts.FindAsync(userId); - if (relatedUser is null) return NotFound("Account was not found."); - - try - { - var relationship = await rels.BlockAccount(currentUser, relatedUser); - return relationship; - } - catch (InvalidOperationException err) - { - return BadRequest(err.Message); - } - } - - [HttpDelete("{userId:guid}/block")] - [Authorize] - public async Task> UnblockUser(Guid userId) - { - if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); - - var relatedUser = await db.Accounts.FindAsync(userId); - if (relatedUser is null) return NotFound("Account was not found."); - - try - { - var relationship = await rels.UnblockAccount(currentUser, relatedUser); - return relationship; - } - catch (InvalidOperationException err) - { - return BadRequest(err.Message); - } - } -} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Account/RelationshipService.cs b/DysonNetwork.Sphere/Account/RelationshipService.cs deleted file mode 100644 index 51df3bf..0000000 --- a/DysonNetwork.Sphere/Account/RelationshipService.cs +++ /dev/null @@ -1,207 +0,0 @@ -using DysonNetwork.Sphere.Storage; -using Microsoft.EntityFrameworkCore; -using NodaTime; - -namespace DysonNetwork.Sphere.Account; - -public class RelationshipService(AppDatabase db, ICacheService cache) -{ - private const string UserFriendsCacheKeyPrefix = "accounts:friends:"; - private const string UserBlockedCacheKeyPrefix = "accounts:blocked:"; - - public async Task HasExistingRelationship(Guid accountId, Guid relatedId) - { - var count = await db.AccountRelationships - .Where(r => (r.AccountId == accountId && r.RelatedId == relatedId) || - (r.AccountId == relatedId && r.AccountId == accountId)) - .CountAsync(); - return count > 0; - } - - public async Task GetRelationship( - Guid accountId, - Guid relatedId, - RelationshipStatus? status = null, - bool ignoreExpired = false - ) - { - var now = Instant.FromDateTimeUtc(DateTime.UtcNow); - var queries = db.AccountRelationships.AsQueryable() - .Where(r => r.AccountId == accountId && r.RelatedId == relatedId); - if (!ignoreExpired) queries = queries.Where(r => r.ExpiredAt == null || r.ExpiredAt > now); - if (status is not null) queries = queries.Where(r => r.Status == status); - var relationship = await queries.FirstOrDefaultAsync(); - return relationship; - } - - public async Task CreateRelationship(Account sender, Account target, RelationshipStatus status) - { - if (status == RelationshipStatus.Pending) - throw new InvalidOperationException( - "Cannot create relationship with pending status, use SendFriendRequest instead."); - if (await HasExistingRelationship(sender.Id, target.Id)) - throw new InvalidOperationException("Found existing relationship between you and target user."); - - var relationship = new Relationship - { - AccountId = sender.Id, - RelatedId = target.Id, - Status = status - }; - - db.AccountRelationships.Add(relationship); - await db.SaveChangesAsync(); - - await PurgeRelationshipCache(sender.Id, target.Id); - - return relationship; - } - - public async Task BlockAccount(Account sender, Account target) - { - if (await HasExistingRelationship(sender.Id, target.Id)) - return await UpdateRelationship(sender.Id, target.Id, RelationshipStatus.Blocked); - return await CreateRelationship(sender, target, RelationshipStatus.Blocked); - } - - public async Task UnblockAccount(Account sender, Account target) - { - var relationship = await GetRelationship(sender.Id, target.Id, RelationshipStatus.Blocked); - if (relationship is null) throw new ArgumentException("There is no relationship between you and the user."); - db.Remove(relationship); - await db.SaveChangesAsync(); - - await PurgeRelationshipCache(sender.Id, target.Id); - - return relationship; - } - - public async Task SendFriendRequest(Account sender, Account target) - { - if (await HasExistingRelationship(sender.Id, target.Id)) - throw new InvalidOperationException("Found existing relationship between you and target user."); - - var relationship = new Relationship - { - AccountId = sender.Id, - RelatedId = target.Id, - Status = RelationshipStatus.Pending, - ExpiredAt = Instant.FromDateTimeUtc(DateTime.UtcNow.AddDays(7)) - }; - - db.AccountRelationships.Add(relationship); - await db.SaveChangesAsync(); - - return relationship; - } - - public async Task DeleteFriendRequest(Guid accountId, Guid relatedId) - { - var relationship = await GetRelationship(accountId, relatedId, RelationshipStatus.Pending); - if (relationship is null) throw new ArgumentException("Friend request was not found."); - - await db.AccountRelationships - .Where(r => r.AccountId == accountId && r.RelatedId == relatedId && r.Status == RelationshipStatus.Pending) - .ExecuteDeleteAsync(); - - await PurgeRelationshipCache(relationship.AccountId, relationship.RelatedId); - } - - public async Task AcceptFriendRelationship( - Relationship relationship, - RelationshipStatus status = RelationshipStatus.Friends - ) - { - if (relationship.Status != RelationshipStatus.Pending) - throw new ArgumentException("Cannot accept friend request that not in pending status."); - if (status == RelationshipStatus.Pending) - throw new ArgumentException("Cannot accept friend request by setting the new status to pending."); - - // Whatever the receiver decides to apply which status to the relationship, - // the sender should always see the user as a friend since the sender ask for it - relationship.Status = RelationshipStatus.Friends; - relationship.ExpiredAt = null; - db.Update(relationship); - - var relationshipBackward = new Relationship - { - AccountId = relationship.RelatedId, - RelatedId = relationship.AccountId, - Status = status - }; - db.AccountRelationships.Add(relationshipBackward); - - await db.SaveChangesAsync(); - - await PurgeRelationshipCache(relationship.AccountId, relationship.RelatedId); - - return relationshipBackward; - } - - public async Task UpdateRelationship(Guid accountId, Guid relatedId, RelationshipStatus status) - { - var relationship = await GetRelationship(accountId, relatedId); - if (relationship is null) throw new ArgumentException("There is no relationship between you and the user."); - if (relationship.Status == status) return relationship; - relationship.Status = status; - db.Update(relationship); - await db.SaveChangesAsync(); - - await PurgeRelationshipCache(accountId, relatedId); - - return relationship; - } - - public async Task> ListAccountFriends(Account account) - { - var cacheKey = $"{UserFriendsCacheKeyPrefix}{account.Id}"; - var friends = await cache.GetAsync>(cacheKey); - - if (friends == null) - { - friends = await db.AccountRelationships - .Where(r => r.RelatedId == account.Id) - .Where(r => r.Status == RelationshipStatus.Friends) - .Select(r => r.AccountId) - .ToListAsync(); - - await cache.SetAsync(cacheKey, friends, TimeSpan.FromHours(1)); - } - - return friends ?? []; - } - - public async Task> ListAccountBlocked(Account account) - { - var cacheKey = $"{UserBlockedCacheKeyPrefix}{account.Id}"; - var blocked = await cache.GetAsync>(cacheKey); - - if (blocked == null) - { - blocked = await db.AccountRelationships - .Where(r => r.RelatedId == account.Id) - .Where(r => r.Status == RelationshipStatus.Blocked) - .Select(r => r.AccountId) - .ToListAsync(); - - await cache.SetAsync(cacheKey, blocked, TimeSpan.FromHours(1)); - } - - return blocked ?? []; - } - - public async Task HasRelationshipWithStatus(Guid accountId, Guid relatedId, - RelationshipStatus status = RelationshipStatus.Friends) - { - var relationship = await GetRelationship(accountId, relatedId, status); - return relationship is not null; - } - - private async Task PurgeRelationshipCache(Guid accountId, Guid relatedId) - { - await cache.RemoveAsync($"{UserFriendsCacheKeyPrefix}{accountId}"); - await cache.RemoveAsync($"{UserFriendsCacheKeyPrefix}{relatedId}"); - await cache.RemoveAsync($"{UserBlockedCacheKeyPrefix}{accountId}"); - await cache.RemoveAsync($"{UserBlockedCacheKeyPrefix}{relatedId}"); - } -} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Account/VerificationMark.cs b/DysonNetwork.Sphere/Account/VerificationMark.cs deleted file mode 100644 index 4f0e6c3..0000000 --- a/DysonNetwork.Sphere/Account/VerificationMark.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System.ComponentModel.DataAnnotations; - -namespace DysonNetwork.Sphere.Account; - -/// -/// The verification info of a resource -/// stands, for it is really an individual or organization or a company in the real world. -/// Besides, it can also be use for mark parody or fake. -/// -public class VerificationMark -{ - public VerificationMarkType Type { get; set; } - [MaxLength(1024)] public string? Title { get; set; } - [MaxLength(8192)] public string? Description { get; set; } - [MaxLength(1024)] public string? VerifiedBy { get; set; } -} - -public enum VerificationMarkType -{ - Official, - Individual, - Organization, - Government, - Creator -} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Activity/ActivityController.cs b/DysonNetwork.Sphere/Activity/ActivityController.cs index 10db5a2..e5bfa26 100644 --- a/DysonNetwork.Sphere/Activity/ActivityController.cs +++ b/DysonNetwork.Sphere/Activity/ActivityController.cs @@ -1,3 +1,4 @@ +using DysonNetwork.Shared.Proto; using Microsoft.AspNetCore.Mvc; using NodaTime; using NodaTime.Text; @@ -45,7 +46,7 @@ public class ActivityController( var debugIncludeSet = debugInclude?.Split(',').ToHashSet() ?? new HashSet(); HttpContext.Items.TryGetValue("CurrentUser", out var currentUserValue); - return currentUserValue is not Account.Account currentUser + return currentUserValue is not Account currentUser ? Ok(await acts.GetActivitiesForAnyone(take, cursorTimestamp, debugIncludeSet)) : Ok(await acts.GetActivities(take, cursorTimestamp, currentUser, filter, debugIncludeSet)); } diff --git a/DysonNetwork.Sphere/Activity/ActivityService.cs b/DysonNetwork.Sphere/Activity/ActivityService.cs index 5adbd50..823c84b 100644 --- a/DysonNetwork.Sphere/Activity/ActivityService.cs +++ b/DysonNetwork.Sphere/Activity/ActivityService.cs @@ -1,5 +1,6 @@ +using DysonNetwork.Shared.Proto; using DysonNetwork.Sphere.Account; -using DysonNetwork.Sphere.Connection.WebReader; +using DysonNetwork.Sphere.WebReader; using DysonNetwork.Sphere.Discovery; using DysonNetwork.Sphere.Post; using DysonNetwork.Sphere.Publisher; @@ -118,14 +119,14 @@ public class ActivityService( public async Task> GetActivities( int take, Instant? cursor, - Account.Account currentUser, + Account currentUser, string? filter = null, HashSet? debugInclude = null ) { var activities = new List(); var userFriends = await rels.ListAccountFriends(currentUser); - var userPublishers = await pub.GetUserPublishers(currentUser.Id); + var userPublishers = await pub.GetUserPublishers(Guid.Parse(currentUser.Id)); debugInclude ??= []; if (string.IsNullOrEmpty(filter)) @@ -190,7 +191,7 @@ public class ActivityService( // Get publishers based on filter var filteredPublishers = filter switch { - "subscriptions" => await pub.GetSubscribedPublishers(currentUser.Id), + "subscriptions" => await pub.GetSubscribedPublishers(Guid.Parse(currentUser.Id)), "friends" => (await pub.GetUserPublishersBatch(userFriends)).SelectMany(x => x.Value) .DistinctBy(x => x.Id) .ToList(), diff --git a/DysonNetwork.Sphere/AppDatabase.cs b/DysonNetwork.Sphere/AppDatabase.cs index 1f635c6..e77bcbc 100644 --- a/DysonNetwork.Sphere/AppDatabase.cs +++ b/DysonNetwork.Sphere/AppDatabase.cs @@ -1,7 +1,5 @@ using System.Linq.Expressions; using System.Reflection; -using DysonNetwork.Sphere.Account; -using DysonNetwork.Sphere.Auth; using DysonNetwork.Sphere.Chat; using DysonNetwork.Sphere.Developer; using DysonNetwork.Sphere.Permission; @@ -9,13 +7,10 @@ using DysonNetwork.Sphere.Post; using DysonNetwork.Sphere.Publisher; using DysonNetwork.Sphere.Realm; using DysonNetwork.Sphere.Sticker; -using DysonNetwork.Sphere.Storage; -using DysonNetwork.Sphere.Wallet; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Design; using Microsoft.EntityFrameworkCore.Query; using NodaTime; -using Npgsql; using Quartz; namespace DysonNetwork.Sphere; @@ -41,27 +36,6 @@ public class AppDatabase( public DbSet PermissionGroups { get; set; } public DbSet PermissionGroupMembers { get; set; } - public DbSet MagicSpells { get; set; } - public DbSet Accounts { get; set; } - public DbSet AccountConnections { get; set; } - public DbSet AccountProfiles { get; set; } - public DbSet AccountContacts { get; set; } - public DbSet AccountAuthFactors { get; set; } - public DbSet AccountRelationships { get; set; } - public DbSet AccountStatuses { get; set; } - public DbSet AccountCheckInResults { get; set; } - public DbSet Notifications { get; set; } - public DbSet NotificationPushSubscriptions { get; set; } - public DbSet Badges { get; set; } - public DbSet ActionLogs { get; set; } - public DbSet AbuseReports { get; set; } - - public DbSet AuthSessions { get; set; } - public DbSet AuthChallenges { get; set; } - - public DbSet Files { get; set; } - public DbSet FileReferences { get; set; } - public DbSet Publishers { get; set; } public DbSet PublisherMembers { get; set; } public DbSet PublisherSubscriptions { get; set; } @@ -87,18 +61,11 @@ public class AppDatabase( public DbSet Stickers { get; set; } public DbSet StickerPacks { get; set; } - public DbSet Wallets { get; set; } - public DbSet WalletPockets { get; set; } - public DbSet PaymentOrders { get; set; } - public DbSet PaymentTransactions { get; set; } - public DbSet CustomApps { get; set; } public DbSet CustomAppSecrets { get; set; } - public DbSet WalletSubscriptions { get; set; } - public DbSet WalletCoupons { get; set; } - public DbSet WebArticles { get; set; } - public DbSet WebFeeds { get; set; } + public DbSet WebArticles { get; set; } + public DbSet WebFeeds { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { @@ -158,17 +125,6 @@ public class AppDatabase( .HasForeignKey(pg => pg.GroupId) .OnDelete(DeleteBehavior.Cascade); - modelBuilder.Entity() - .HasKey(r => new { FromAccountId = r.AccountId, ToAccountId = r.RelatedId }); - modelBuilder.Entity() - .HasOne(r => r.Account) - .WithMany(a => a.OutgoingRelationships) - .HasForeignKey(r => r.AccountId); - modelBuilder.Entity() - .HasOne(r => r.Related) - .WithMany(a => a.IncomingRelationships) - .HasForeignKey(r => r.RelatedId); - modelBuilder.Entity() .HasKey(pm => new { pm.PublisherId, pm.AccountId }); modelBuilder.Entity() @@ -176,21 +132,11 @@ public class AppDatabase( .WithMany(p => p.Members) .HasForeignKey(pm => pm.PublisherId) .OnDelete(DeleteBehavior.Cascade); - modelBuilder.Entity() - .HasOne(pm => pm.Account) - .WithMany() - .HasForeignKey(pm => pm.AccountId) - .OnDelete(DeleteBehavior.Cascade); modelBuilder.Entity() .HasOne(ps => ps.Publisher) .WithMany(p => p.Subscriptions) .HasForeignKey(ps => ps.PublisherId) .OnDelete(DeleteBehavior.Cascade); - modelBuilder.Entity() - .HasOne(ps => ps.Account) - .WithMany() - .HasForeignKey(ps => ps.AccountId) - .OnDelete(DeleteBehavior.Cascade); modelBuilder.Entity() .HasGeneratedTsVectorColumn(p => p.SearchVector, "simple", p => new { p.Title, p.Description, p.Content }) @@ -237,11 +183,6 @@ public class AppDatabase( .WithMany(p => p.Members) .HasForeignKey(pm => pm.RealmId) .OnDelete(DeleteBehavior.Cascade); - modelBuilder.Entity() - .HasOne(pm => pm.Account) - .WithMany() - .HasForeignKey(pm => pm.AccountId) - .OnDelete(DeleteBehavior.Cascade); modelBuilder.Entity() .HasKey(rt => new { rt.RealmId, rt.TagId }); @@ -265,11 +206,6 @@ public class AppDatabase( .WithMany(p => p.Members) .HasForeignKey(pm => pm.ChatRoomId) .OnDelete(DeleteBehavior.Cascade); - modelBuilder.Entity() - .HasOne(pm => pm.Account) - .WithMany() - .HasForeignKey(pm => pm.AccountId) - .OnDelete(DeleteBehavior.Cascade); modelBuilder.Entity() .HasOne(m => m.ForwardedMessage) .WithMany() @@ -291,11 +227,10 @@ public class AppDatabase( .HasForeignKey(m => m.SenderId) .OnDelete(DeleteBehavior.Cascade); - modelBuilder.Entity() + modelBuilder.Entity() .HasIndex(f => f.Url) .IsUnique(); - - modelBuilder.Entity() + modelBuilder.Entity() .HasIndex(a => a.Url) .IsUnique(); @@ -356,13 +291,8 @@ public class AppDatabaseRecyclingJob(AppDatabase db, ILogger x.ExpiredAt != null && x.ExpiredAt <= now) - .ExecuteDeleteAsync(); - logger.LogDebug("Removed {Count} records of expired relationships.", affectedRows); // Expired permission group members - affectedRows = await db.PermissionGroupMembers + var affectedRows = await db.PermissionGroupMembers .Where(x => x.ExpiredAt != null && x.ExpiredAt <= now) .ExecuteDeleteAsync(); logger.LogDebug("Removed {Count} records of expired permission group members.", affectedRows); diff --git a/DysonNetwork.Sphere/Auth/Auth.cs b/DysonNetwork.Sphere/Auth/Auth.cs deleted file mode 100644 index 135f6bd..0000000 --- a/DysonNetwork.Sphere/Auth/Auth.cs +++ /dev/null @@ -1,279 +0,0 @@ -using System.IdentityModel.Tokens.Jwt; -using System.Security.Claims; -using System.Security.Cryptography; -using System.Text.Encodings.Web; -using DysonNetwork.Sphere.Account; -using DysonNetwork.Sphere.Auth.OidcProvider.Options; -using DysonNetwork.Sphere.Storage; -using DysonNetwork.Sphere.Storage.Handlers; -using Microsoft.AspNetCore.Authentication; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Options; -using Microsoft.IdentityModel.Tokens; -using NodaTime; -using System.Text; -using DysonNetwork.Sphere.Auth.OidcProvider.Controllers; -using DysonNetwork.Sphere.Auth.OidcProvider.Services; -using SystemClock = NodaTime.SystemClock; - -namespace DysonNetwork.Sphere.Auth; - -public static class AuthConstants -{ - public const string SchemeName = "DysonToken"; - public const string TokenQueryParamName = "tk"; - public const string CookieTokenName = "AuthToken"; -} - -public enum TokenType -{ - AuthKey, - ApiKey, - OidcKey, - Unknown -} - -public class TokenInfo -{ - public string Token { get; set; } = string.Empty; - public TokenType Type { get; set; } = TokenType.Unknown; -} - -public class DysonTokenAuthOptions : AuthenticationSchemeOptions; - -public class DysonTokenAuthHandler( - IOptionsMonitor options, - IConfiguration configuration, - ILoggerFactory logger, - UrlEncoder encoder, - AppDatabase database, - OidcProviderService oidc, - ICacheService cache, - FlushBufferService fbs -) - : AuthenticationHandler(options, logger, encoder) -{ - public const string AuthCachePrefix = "auth:"; - - protected override async Task HandleAuthenticateAsync() - { - var tokenInfo = _ExtractToken(Request); - - if (tokenInfo == null || string.IsNullOrEmpty(tokenInfo.Token)) - return AuthenticateResult.Fail("No token was provided."); - - try - { - var now = SystemClock.Instance.GetCurrentInstant(); - - // Validate token and extract session ID - if (!ValidateToken(tokenInfo.Token, out var sessionId)) - return AuthenticateResult.Fail("Invalid token."); - - // Try to get session from cache first - var session = await cache.GetAsync($"{AuthCachePrefix}{sessionId}"); - - // If not in cache, load from database - if (session is null) - { - session = await database.AuthSessions - .Where(e => e.Id == sessionId) - .Include(e => e.Challenge) - .Include(e => e.Account) - .ThenInclude(e => e.Profile) - .FirstOrDefaultAsync(); - - if (session is not null) - { - // Store in cache for future requests - await cache.SetWithGroupsAsync( - $"auth:{sessionId}", - session, - [$"{AccountService.AccountCachePrefix}{session.Account.Id}"], - TimeSpan.FromHours(1) - ); - } - } - - // Check if the session exists - if (session == null) - return AuthenticateResult.Fail("Session not found."); - - // Check if the session is expired - if (session.ExpiredAt.HasValue && session.ExpiredAt.Value < now) - return AuthenticateResult.Fail("Session expired."); - - // Store user and session in the HttpContext.Items for easy access in controllers - Context.Items["CurrentUser"] = session.Account; - Context.Items["CurrentSession"] = session; - Context.Items["CurrentTokenType"] = tokenInfo.Type.ToString(); - - // Create claims from the session - var claims = new List - { - new("user_id", session.Account.Id.ToString()), - new("session_id", session.Id.ToString()), - new("token_type", tokenInfo.Type.ToString()) - }; - - // Add scopes as claims - session.Challenge.Scopes.ForEach(scope => claims.Add(new Claim("scope", scope))); - - // Add superuser claim if applicable - if (session.Account.IsSuperuser) - claims.Add(new Claim("is_superuser", "1")); - - // Create the identity and principal - var identity = new ClaimsIdentity(claims, AuthConstants.SchemeName); - var principal = new ClaimsPrincipal(identity); - - var ticket = new AuthenticationTicket(principal, AuthConstants.SchemeName); - - var lastInfo = new LastActiveInfo - { - Account = session.Account, - Session = session, - SeenAt = SystemClock.Instance.GetCurrentInstant(), - }; - fbs.Enqueue(lastInfo); - - return AuthenticateResult.Success(ticket); - } - catch (Exception ex) - { - return AuthenticateResult.Fail($"Authentication failed: {ex.Message}"); - } - } - - private bool ValidateToken(string token, out Guid sessionId) - { - sessionId = Guid.Empty; - - try - { - var parts = token.Split('.'); - - switch (parts.Length) - { - // Handle JWT tokens (3 parts) - case 3: - { - var (isValid, jwtResult) = oidc.ValidateToken(token); - if (!isValid) return false; - var jti = jwtResult?.Claims.FirstOrDefault(c => c.Type == "jti")?.Value; - if (jti is null) return false; - - return Guid.TryParse(jti, out sessionId); - } - // Handle compact tokens (2 parts) - case 2: - // Original compact token validation logic - try - { - // Decode the payload - var payloadBytes = Base64UrlDecode(parts[0]); - - // Extract session ID - sessionId = new Guid(payloadBytes); - - // Load public key for verification - var publicKeyPem = File.ReadAllText(configuration["AuthToken:PublicKeyPath"]!); - using var rsa = RSA.Create(); - rsa.ImportFromPem(publicKeyPem); - - // Verify signature - var signature = Base64UrlDecode(parts[1]); - return rsa.VerifyData(payloadBytes, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); - } - catch - { - return false; - } - - break; - default: - return false; - } - } - catch (Exception ex) - { - Logger.LogWarning(ex, "Token validation failed"); - return false; - } - } - - private static byte[] Base64UrlDecode(string base64Url) - { - var padded = base64Url - .Replace('-', '+') - .Replace('_', '/'); - - switch (padded.Length % 4) - { - case 2: padded += "=="; break; - case 3: padded += "="; break; - } - - return Convert.FromBase64String(padded); - } - - private TokenInfo? _ExtractToken(HttpRequest request) - { - // Check for token in query parameters - if (request.Query.TryGetValue(AuthConstants.TokenQueryParamName, out var queryToken)) - { - return new TokenInfo - { - Token = queryToken.ToString(), - Type = TokenType.AuthKey - }; - } - - - // Check for token in Authorization header - var authHeader = request.Headers.Authorization.ToString(); - if (!string.IsNullOrEmpty(authHeader)) - { - if (authHeader.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase)) - { - var token = authHeader["Bearer ".Length..].Trim(); - var parts = token.Split('.'); - - return new TokenInfo - { - Token = token, - Type = parts.Length == 3 ? TokenType.OidcKey : TokenType.AuthKey - }; - } - else if (authHeader.StartsWith("AtField ", StringComparison.OrdinalIgnoreCase)) - { - return new TokenInfo - { - Token = authHeader["AtField ".Length..].Trim(), - Type = TokenType.AuthKey - }; - } - else if (authHeader.StartsWith("AkField ", StringComparison.OrdinalIgnoreCase)) - { - return new TokenInfo - { - Token = authHeader["AkField ".Length..].Trim(), - Type = TokenType.ApiKey - }; - } - } - - // Check for token in cookies - if (request.Cookies.TryGetValue(AuthConstants.CookieTokenName, out var cookieToken)) - { - return new TokenInfo - { - Token = cookieToken, - Type = cookieToken.Count(c => c == '.') == 2 ? TokenType.OidcKey : TokenType.AuthKey - }; - } - - - return null; - } -} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Auth/AuthController.cs b/DysonNetwork.Sphere/Auth/AuthController.cs deleted file mode 100644 index e34769c..0000000 --- a/DysonNetwork.Sphere/Auth/AuthController.cs +++ /dev/null @@ -1,269 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using DysonNetwork.Sphere.Account; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using NodaTime; -using Microsoft.EntityFrameworkCore; -using System.IdentityModel.Tokens.Jwt; -using System.Text.Json; -using DysonNetwork.Sphere.Connection; - -namespace DysonNetwork.Sphere.Auth; - -[ApiController] -[Route("/api/auth")] -public class AuthController( - AppDatabase db, - AccountService accounts, - AuthService auth, - GeoIpService geo, - ActionLogService als -) : ControllerBase -{ - public class ChallengeRequest - { - [Required] public ChallengePlatform Platform { get; set; } - [Required] [MaxLength(256)] public string Account { get; set; } = null!; - [Required] [MaxLength(512)] public string DeviceId { get; set; } = null!; - public List Audiences { get; set; } = new(); - public List Scopes { get; set; } = new(); - } - - [HttpPost("challenge")] - public async Task> StartChallenge([FromBody] ChallengeRequest request) - { - var account = await accounts.LookupAccount(request.Account); - if (account is null) return NotFound("Account was not found."); - - var ipAddress = HttpContext.Connection.RemoteIpAddress?.ToString(); - var userAgent = HttpContext.Request.Headers.UserAgent.ToString(); - - var now = Instant.FromDateTimeUtc(DateTime.UtcNow); - - // Trying to pick up challenges from the same IP address and user agent - var existingChallenge = await db.AuthChallenges - .Where(e => e.Account == account) - .Where(e => e.IpAddress == ipAddress) - .Where(e => e.UserAgent == userAgent) - .Where(e => e.StepRemain > 0) - .Where(e => e.ExpiredAt != null && now < e.ExpiredAt) - .FirstOrDefaultAsync(); - if (existingChallenge is not null) return existingChallenge; - - var challenge = new Challenge - { - ExpiredAt = Instant.FromDateTimeUtc(DateTime.UtcNow.AddHours(1)), - StepTotal = await auth.DetectChallengeRisk(Request, account), - Platform = request.Platform, - Audiences = request.Audiences, - Scopes = request.Scopes, - IpAddress = ipAddress, - UserAgent = userAgent, - Location = geo.GetPointFromIp(ipAddress), - DeviceId = request.DeviceId, - AccountId = account.Id - }.Normalize(); - - await db.AuthChallenges.AddAsync(challenge); - await db.SaveChangesAsync(); - - als.CreateActionLogFromRequest(ActionLogType.ChallengeAttempt, - new Dictionary { { "challenge_id", challenge.Id } }, Request, account - ); - - return challenge; - } - - [HttpGet("challenge/{id:guid}")] - public async Task> GetChallenge([FromRoute] Guid id) - { - var challenge = await db.AuthChallenges - .Include(e => e.Account) - .ThenInclude(e => e.Profile) - .FirstOrDefaultAsync(e => e.Id == id); - - return challenge is null - ? NotFound("Auth challenge was not found.") - : challenge; - } - - [HttpGet("challenge/{id:guid}/factors")] - public async Task>> GetChallengeFactors([FromRoute] Guid id) - { - var challenge = await db.AuthChallenges - .Include(e => e.Account) - .Include(e => e.Account.AuthFactors) - .Where(e => e.Id == id) - .FirstOrDefaultAsync(); - return challenge is null - ? NotFound("Auth challenge was not found.") - : challenge.Account.AuthFactors.Where(e => e is { EnabledAt: not null, Trustworthy: >= 1 }).ToList(); - } - - [HttpPost("challenge/{id:guid}/factors/{factorId:guid}")] - public async Task RequestFactorCode( - [FromRoute] Guid id, - [FromRoute] Guid factorId, - [FromBody] string? hint - ) - { - var challenge = await db.AuthChallenges - .Include(e => e.Account) - .Where(e => e.Id == id).FirstOrDefaultAsync(); - if (challenge is null) return NotFound("Auth challenge was not found."); - var factor = await db.AccountAuthFactors - .Where(e => e.Id == factorId) - .Where(e => e.Account == challenge.Account).FirstOrDefaultAsync(); - if (factor is null) return NotFound("Auth factor was not found."); - - try - { - await accounts.SendFactorCode(challenge.Account, factor, hint); - } - catch (Exception ex) - { - return BadRequest(ex.Message); - } - - return Ok(); - } - - public class PerformChallengeRequest - { - [Required] public Guid FactorId { get; set; } - [Required] public string Password { get; set; } = string.Empty; - } - - [HttpPatch("challenge/{id:guid}")] - public async Task> DoChallenge( - [FromRoute] Guid id, - [FromBody] PerformChallengeRequest request - ) - { - var challenge = await db.AuthChallenges.Include(e => e.Account).FirstOrDefaultAsync(e => e.Id == id); - if (challenge is null) return NotFound("Auth challenge was not found."); - - var factor = await db.AccountAuthFactors.FindAsync(request.FactorId); - if (factor is null) return NotFound("Auth factor was not found."); - if (factor.EnabledAt is null) return BadRequest("Auth factor is not enabled."); - if (factor.Trustworthy <= 0) return BadRequest("Auth factor is not trustworthy."); - - if (challenge.StepRemain == 0) return challenge; - if (challenge.ExpiredAt.HasValue && challenge.ExpiredAt.Value < Instant.FromDateTimeUtc(DateTime.UtcNow)) - return BadRequest(); - - try - { - if (await accounts.VerifyFactorCode(factor, request.Password)) - { - challenge.StepRemain -= factor.Trustworthy; - challenge.StepRemain = Math.Max(0, challenge.StepRemain); - challenge.BlacklistFactors.Add(factor.Id); - db.Update(challenge); - als.CreateActionLogFromRequest(ActionLogType.ChallengeSuccess, - new Dictionary - { - { "challenge_id", challenge.Id }, - { "factor_id", factor.Id } - }, Request, challenge.Account - ); - } - else - { - throw new ArgumentException("Invalid password."); - } - } - catch - { - challenge.FailedAttempts++; - db.Update(challenge); - als.CreateActionLogFromRequest(ActionLogType.ChallengeFailure, - new Dictionary - { - { "challenge_id", challenge.Id }, - { "factor_id", factor.Id } - }, Request, challenge.Account - ); - await db.SaveChangesAsync(); - return BadRequest("Invalid password."); - } - - if (challenge.StepRemain == 0) - { - als.CreateActionLogFromRequest(ActionLogType.NewLogin, - new Dictionary - { - { "challenge_id", challenge.Id }, - { "account_id", challenge.AccountId } - }, Request, challenge.Account - ); - } - - await db.SaveChangesAsync(); - return challenge; - } - - public class TokenExchangeRequest - { - public string GrantType { get; set; } = string.Empty; - public string? RefreshToken { get; set; } - public string? Code { get; set; } - } - - public class TokenExchangeResponse - { - public string Token { get; set; } = string.Empty; - } - - [HttpPost("token")] - public async Task> ExchangeToken([FromBody] TokenExchangeRequest request) - { - switch (request.GrantType) - { - case "authorization_code": - var code = Guid.TryParse(request.Code, out var codeId) ? codeId : Guid.Empty; - if (code == Guid.Empty) - return BadRequest("Invalid or missing authorization code."); - var challenge = await db.AuthChallenges - .Include(e => e.Account) - .Where(e => e.Id == code) - .FirstOrDefaultAsync(); - if (challenge is null) - return BadRequest("Authorization code not found or expired."); - if (challenge.StepRemain != 0) - return BadRequest("Challenge not yet completed."); - - var session = await db.AuthSessions - .Where(e => e.Challenge == challenge) - .FirstOrDefaultAsync(); - if (session is not null) - return BadRequest("Session already exists for this challenge."); - - session = new Session - { - LastGrantedAt = Instant.FromDateTimeUtc(DateTime.UtcNow), - ExpiredAt = Instant.FromDateTimeUtc(DateTime.UtcNow.AddDays(30)), - Account = challenge.Account, - Challenge = challenge, - }; - - db.AuthSessions.Add(session); - await db.SaveChangesAsync(); - - var tk = auth.CreateToken(session); - return Ok(new TokenExchangeResponse { Token = tk }); - case "refresh_token": - // Since we no longer need the refresh token - // This case is blank for now, thinking to mock it if the OIDC standard requires it - default: - return BadRequest("Unsupported grant type."); - } - } - - [HttpPost("captcha")] - public async Task ValidateCaptcha([FromBody] string token) - { - var result = await auth.ValidateCaptcha(token); - return result ? Ok() : BadRequest(); - } -} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Auth/AuthService.cs b/DysonNetwork.Sphere/Auth/AuthService.cs deleted file mode 100644 index d0c2ee3..0000000 --- a/DysonNetwork.Sphere/Auth/AuthService.cs +++ /dev/null @@ -1,304 +0,0 @@ -using System.Security.Cryptography; -using System.Text.Json; -using DysonNetwork.Sphere.Account; -using DysonNetwork.Sphere.Storage; -using Microsoft.EntityFrameworkCore; -using NodaTime; - -namespace DysonNetwork.Sphere.Auth; - -public class AuthService( - AppDatabase db, - IConfiguration config, - IHttpClientFactory httpClientFactory, - IHttpContextAccessor httpContextAccessor, - ICacheService cache -) -{ - private HttpContext HttpContext => httpContextAccessor.HttpContext!; - - /// - /// Detect the risk of the current request to login - /// and returns the required steps to login. - /// - /// The request context - /// The account to login - /// The required steps to login - public async Task DetectChallengeRisk(HttpRequest request, Account.Account account) - { - // 1) Find out how many authentication factors the account has enabled. - var maxSteps = await db.AccountAuthFactors - .Where(f => f.AccountId == account.Id) - .Where(f => f.EnabledAt != null) - .CountAsync(); - - // We’ll accumulate a “risk score” based on various factors. - // Then we can decide how many total steps are required for the challenge. - var riskScore = 0; - - // 2) Get the remote IP address from the request (if any). - var ipAddress = request.HttpContext.Connection.RemoteIpAddress?.ToString(); - var lastActiveInfo = await db.AuthSessions - .OrderByDescending(s => s.LastGrantedAt) - .Include(s => s.Challenge) - .Where(s => s.AccountId == account.Id) - .FirstOrDefaultAsync(); - - // Example check: if IP is missing or in an unusual range, increase the risk. - // (This is just a placeholder; in reality, you’d integrate with GeoIpService or a custom check.) - if (string.IsNullOrWhiteSpace(ipAddress)) - riskScore += 1; - else - { - if (!string.IsNullOrEmpty(lastActiveInfo?.Challenge.IpAddress) && - !lastActiveInfo.Challenge.IpAddress.Equals(ipAddress, StringComparison.OrdinalIgnoreCase)) - riskScore += 1; - } - - // 3) (Optional) Check how recent the last login was. - // If it was a long time ago, the risk might be higher. - var now = SystemClock.Instance.GetCurrentInstant(); - var daysSinceLastActive = lastActiveInfo?.LastGrantedAt is not null - ? (now - lastActiveInfo.LastGrantedAt.Value).TotalDays - : double.MaxValue; - if (daysSinceLastActive > 30) - riskScore += 1; - - // 4) Combine base “maxSteps” (the number of enabled factors) with any accumulated risk score. - const int totalRiskScore = 3; - var totalRequiredSteps = (int)Math.Round((float)maxSteps * riskScore / totalRiskScore); - // Clamp the steps - totalRequiredSteps = Math.Max(Math.Min(totalRequiredSteps, maxSteps), 1); - - return totalRequiredSteps; - } - - public async Task CreateSessionForOidcAsync(Account.Account account, Instant time, Guid? customAppId = null) - { - var challenge = new Challenge - { - 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 - }; - - var session = new Session - { - AccountId = account.Id, - CreatedAt = time, - LastGrantedAt = time, - Challenge = challenge, - AppId = customAppId - }; - - db.AuthChallenges.Add(challenge); - db.AuthSessions.Add(session); - await db.SaveChangesAsync(); - - return session; - } - - public async Task ValidateCaptcha(string token) - { - if (string.IsNullOrWhiteSpace(token)) return false; - - var provider = config.GetSection("Captcha")["Provider"]?.ToLower(); - var apiSecret = config.GetSection("Captcha")["ApiSecret"]; - - var client = httpClientFactory.CreateClient(); - - var jsonOpts = new JsonSerializerOptions - { - PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower, - DictionaryKeyPolicy = JsonNamingPolicy.SnakeCaseLower - }; - - switch (provider) - { - case "cloudflare": - var content = new StringContent($"secret={apiSecret}&response={token}", System.Text.Encoding.UTF8, - "application/x-www-form-urlencoded"); - var response = await client.PostAsync("https://challenges.cloudflare.com/turnstile/v0/siteverify", - content); - response.EnsureSuccessStatusCode(); - - var json = await response.Content.ReadAsStringAsync(); - var result = JsonSerializer.Deserialize(json, options: jsonOpts); - - return result?.Success == true; - case "google": - content = new StringContent($"secret={apiSecret}&response={token}", System.Text.Encoding.UTF8, - "application/x-www-form-urlencoded"); - response = await client.PostAsync("https://www.google.com/recaptcha/siteverify", content); - response.EnsureSuccessStatusCode(); - - json = await response.Content.ReadAsStringAsync(); - result = JsonSerializer.Deserialize(json, options: jsonOpts); - - return result?.Success == true; - case "hcaptcha": - content = new StringContent($"secret={apiSecret}&response={token}", System.Text.Encoding.UTF8, - "application/x-www-form-urlencoded"); - response = await client.PostAsync("https://hcaptcha.com/siteverify", content); - response.EnsureSuccessStatusCode(); - - json = await response.Content.ReadAsStringAsync(); - result = JsonSerializer.Deserialize(json, options: jsonOpts); - - return result?.Success == true; - default: - throw new ArgumentException("The server misconfigured for the captcha."); - } - } - - public string CreateToken(Session session) - { - // Load the private key for signing - var privateKeyPem = File.ReadAllText(config["AuthToken:PrivateKeyPath"]!); - using var rsa = RSA.Create(); - rsa.ImportFromPem(privateKeyPem); - - // Create and return a single token - return CreateCompactToken(session.Id, rsa); - } - - private string CreateCompactToken(Guid sessionId, RSA rsa) - { - // Create the payload: just the session ID - var payloadBytes = sessionId.ToByteArray(); - - // Base64Url encode the payload - var payloadBase64 = Base64UrlEncode(payloadBytes); - - // Sign the payload with RSA-SHA256 - var signature = rsa.SignData(payloadBytes, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); - - // Base64Url encode the signature - var signatureBase64 = Base64UrlEncode(signature); - - // Combine payload and signature with a dot - return $"{payloadBase64}.{signatureBase64}"; - } - - public async Task ValidateSudoMode(Session session, string? pinCode) - { - // Check if the session is already in sudo mode (cached) - var sudoModeKey = $"accounts:{session.Id}:sudo"; - var (found, _) = await cache.GetAsyncWithStatus(sudoModeKey); - - if (found) - { - // Session is already in sudo mode - return true; - } - - // Check if the user has a pin code - var hasPinCode = await db.AccountAuthFactors - .Where(f => f.AccountId == session.AccountId) - .Where(f => f.EnabledAt != null) - .Where(f => f.Type == AccountAuthFactorType.PinCode) - .AnyAsync(); - - if (!hasPinCode) - { - // User doesn't have a pin code, no validation needed - return true; - } - - // If pin code is not provided, we can't validate - if (string.IsNullOrEmpty(pinCode)) - { - return false; - } - - try - { - // Validate the pin code - var isValid = await ValidatePinCode(session.AccountId, pinCode); - - if (isValid) - { - // Set session in sudo mode for 5 minutes - await cache.SetAsync(sudoModeKey, true, TimeSpan.FromMinutes(5)); - } - - return isValid; - } - catch (InvalidOperationException) - { - // No pin code enabled for this account, so validation is successful - return true; - } - } - - public async Task ValidatePinCode(Guid accountId, string pinCode) - { - var factor = await db.AccountAuthFactors - .Where(f => f.AccountId == accountId) - .Where(f => f.EnabledAt != null) - .Where(f => f.Type == AccountAuthFactorType.PinCode) - .FirstOrDefaultAsync(); - if (factor is null) throw new InvalidOperationException("No pin code enabled for this account."); - - return factor.VerifyPassword(pinCode); - } - - public bool ValidateToken(string token, out Guid sessionId) - { - sessionId = Guid.Empty; - - try - { - // Split the token - var parts = token.Split('.'); - if (parts.Length != 2) - return false; - - // Decode the payload - var payloadBytes = Base64UrlDecode(parts[0]); - - // Extract session ID - sessionId = new Guid(payloadBytes); - - // Load public key for verification - var publicKeyPem = File.ReadAllText(config["AuthToken:PublicKeyPath"]!); - using var rsa = RSA.Create(); - rsa.ImportFromPem(publicKeyPem); - - // Verify signature - var signature = Base64UrlDecode(parts[1]); - return rsa.VerifyData(payloadBytes, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); - } - catch - { - return false; - } - } - - // Helper methods for Base64Url encoding/decoding - private static string Base64UrlEncode(byte[] data) - { - return Convert.ToBase64String(data) - .TrimEnd('=') - .Replace('+', '-') - .Replace('/', '_'); - } - - private static byte[] Base64UrlDecode(string base64Url) - { - string padded = base64Url - .Replace('-', '+') - .Replace('_', '/'); - - switch (padded.Length % 4) - { - case 2: padded += "=="; break; - case 3: padded += "="; break; - } - - return Convert.FromBase64String(padded); - } -} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Auth/CheckpointModel.cs b/DysonNetwork.Sphere/Auth/CheckpointModel.cs deleted file mode 100644 index 3b4ea12..0000000 --- a/DysonNetwork.Sphere/Auth/CheckpointModel.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace DysonNetwork.Sphere.Auth; - -public class CaptchaVerificationResponse -{ - public bool Success { get; set; } -} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Auth/CompactTokenService.cs b/DysonNetwork.Sphere/Auth/CompactTokenService.cs deleted file mode 100644 index 53c3e8b..0000000 --- a/DysonNetwork.Sphere/Auth/CompactTokenService.cs +++ /dev/null @@ -1,94 +0,0 @@ -using System.Security.Cryptography; - -namespace DysonNetwork.Sphere.Auth; - -public class CompactTokenService(IConfiguration config) -{ - private readonly string _privateKeyPath = config["AuthToken:PrivateKeyPath"] - ?? throw new InvalidOperationException("AuthToken:PrivateKeyPath configuration is missing"); - - public string CreateToken(Session session) - { - // Load the private key for signing - var privateKeyPem = File.ReadAllText(_privateKeyPath); - using var rsa = RSA.Create(); - rsa.ImportFromPem(privateKeyPem); - - // Create and return a single token - return CreateCompactToken(session.Id, rsa); - } - - private string CreateCompactToken(Guid sessionId, RSA rsa) - { - // Create the payload: just the session ID - var payloadBytes = sessionId.ToByteArray(); - - // Base64Url encode the payload - var payloadBase64 = Base64UrlEncode(payloadBytes); - - // Sign the payload with RSA-SHA256 - var signature = rsa.SignData(payloadBytes, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); - - // Base64Url encode the signature - var signatureBase64 = Base64UrlEncode(signature); - - // Combine payload and signature with a dot - return $"{payloadBase64}.{signatureBase64}"; - } - - public bool ValidateToken(string token, out Guid sessionId) - { - sessionId = Guid.Empty; - - try - { - // Split the token - var parts = token.Split('.'); - if (parts.Length != 2) - return false; - - // Decode the payload - var payloadBytes = Base64UrlDecode(parts[0]); - - // Extract session ID - sessionId = new Guid(payloadBytes); - - // Load public key for verification - var publicKeyPem = File.ReadAllText(config["AuthToken:PublicKeyPath"]!); - using var rsa = RSA.Create(); - rsa.ImportFromPem(publicKeyPem); - - // Verify signature - var signature = Base64UrlDecode(parts[1]); - return rsa.VerifyData(payloadBytes, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); - } - catch - { - return false; - } - } - - // Helper methods for Base64Url encoding/decoding - private static string Base64UrlEncode(byte[] data) - { - return Convert.ToBase64String(data) - .TrimEnd('=') - .Replace('+', '-') - .Replace('/', '_'); - } - - private static byte[] Base64UrlDecode(string base64Url) - { - string padded = base64Url - .Replace('-', '+') - .Replace('_', '/'); - - switch (padded.Length % 4) - { - case 2: padded += "=="; break; - case 3: padded += "="; break; - } - - return Convert.FromBase64String(padded); - } -} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Auth/OidcProvider/Controllers/OidcProviderController.cs b/DysonNetwork.Sphere/Auth/OidcProvider/Controllers/OidcProviderController.cs deleted file mode 100644 index 847925c..0000000 --- a/DysonNetwork.Sphere/Auth/OidcProvider/Controllers/OidcProviderController.cs +++ /dev/null @@ -1,242 +0,0 @@ -using System.Security.Cryptography; -using System.Text; -using DysonNetwork.Sphere.Auth.OidcProvider.Options; -using DysonNetwork.Sphere.Auth.OidcProvider.Responses; -using DysonNetwork.Sphere.Auth.OidcProvider.Services; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Options; -using System.Text.Json.Serialization; -using DysonNetwork.Sphere.Account; -using Microsoft.EntityFrameworkCore; -using Microsoft.IdentityModel.Tokens; -using NodaTime; - -namespace DysonNetwork.Sphere.Auth.OidcProvider.Controllers; - -[Route("/api/auth/open")] -[ApiController] -public class OidcProviderController( - AppDatabase db, - OidcProviderService oidcService, - IConfiguration configuration, - IOptions options, - ILogger logger -) - : ControllerBase -{ - [HttpPost("token")] - [Consumes("application/x-www-form-urlencoded")] - public async Task Token([FromForm] TokenRequest request) - { - switch (request.GrantType) - { - // Validate client credentials - case "authorization_code" when request.ClientId == null || string.IsNullOrEmpty(request.ClientSecret): - return BadRequest("Client credentials are required"); - case "authorization_code" when request.Code == null: - return BadRequest("Authorization code is required"); - case "authorization_code": - { - var client = await oidcService.FindClientByIdAsync(request.ClientId.Value); - if (client == null || - !await oidcService.ValidateClientCredentialsAsync(request.ClientId.Value, request.ClientSecret)) - return BadRequest(new ErrorResponse - { Error = "invalid_client", ErrorDescription = "Invalid client credentials" }); - - // Generate tokens - var tokenResponse = await oidcService.GenerateTokenResponseAsync( - clientId: request.ClientId.Value, - authorizationCode: request.Code!, - redirectUri: request.RedirectUri, - codeVerifier: request.CodeVerifier - ); - - return Ok(tokenResponse); - } - case "refresh_token" when string.IsNullOrEmpty(request.RefreshToken): - return BadRequest(new ErrorResponse - { Error = "invalid_request", ErrorDescription = "Refresh token is required" }); - case "refresh_token": - { - try - { - // Decode the base64 refresh token to get the session ID - var sessionIdBytes = Convert.FromBase64String(request.RefreshToken); - var sessionId = new Guid(sessionIdBytes); - - // Find the session and related data - var session = await oidcService.FindSessionByIdAsync(sessionId); - var now = SystemClock.Instance.GetCurrentInstant(); - if (session?.App is null || session.ExpiredAt < now) - { - return BadRequest(new ErrorResponse - { - Error = "invalid_grant", - ErrorDescription = "Invalid or expired refresh token" - }); - } - - // Get the client - var client = session.App; - if (client == null) - { - return BadRequest(new ErrorResponse - { - Error = "invalid_client", - ErrorDescription = "Client not found" - }); - } - - // Generate new tokens - var tokenResponse = await oidcService.GenerateTokenResponseAsync( - clientId: session.AppId!.Value, - sessionId: session.Id - ); - - return Ok(tokenResponse); - } - catch (FormatException) - { - return BadRequest(new ErrorResponse - { - Error = "invalid_grant", - ErrorDescription = "Invalid refresh token format" - }); - } - } - default: - return BadRequest(new ErrorResponse { Error = "unsupported_grant_type" }); - } - } - - [HttpGet("userinfo")] - [Authorize] - public async Task GetUserInfo() - { - if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser || - HttpContext.Items["CurrentSession"] is not Session currentSession) return Unauthorized(); - - // Get requested scopes from the token - var scopes = currentSession.Challenge.Scopes; - - var userInfo = new Dictionary - { - ["sub"] = currentUser.Id - }; - - // Include standard claims based on scopes - if (scopes.Contains("profile") || scopes.Contains("name")) - { - userInfo["name"] = currentUser.Name; - userInfo["preferred_username"] = currentUser.Nick; - } - - var userEmail = await db.AccountContacts - .Where(c => c.Type == AccountContactType.Email && c.AccountId == currentUser.Id) - .FirstOrDefaultAsync(); - if (scopes.Contains("email") && userEmail is not null) - { - userInfo["email"] = userEmail.Content; - userInfo["email_verified"] = userEmail.VerifiedAt is not null; - } - - return Ok(userInfo); - } - - [HttpGet("/.well-known/openid-configuration")] - public IActionResult GetConfiguration() - { - var baseUrl = configuration["BaseUrl"]; - var issuer = options.Value.IssuerUri.TrimEnd('/'); - - return Ok(new - { - issuer = issuer, - authorization_endpoint = $"{baseUrl}/auth/authorize", - token_endpoint = $"{baseUrl}/auth/open/token", - userinfo_endpoint = $"{baseUrl}/auth/open/userinfo", - jwks_uri = $"{baseUrl}/.well-known/jwks", - scopes_supported = new[] { "openid", "profile", "email" }, - response_types_supported = new[] - { "code", "token", "id_token", "code token", "code id_token", "token id_token", "code token id_token" }, - grant_types_supported = new[] { "authorization_code", "refresh_token" }, - token_endpoint_auth_methods_supported = new[] { "client_secret_basic", "client_secret_post" }, - id_token_signing_alg_values_supported = new[] { "HS256" }, - subject_types_supported = new[] { "public" }, - claims_supported = new[] { "sub", "name", "email", "email_verified" }, - code_challenge_methods_supported = new[] { "S256" }, - response_modes_supported = new[] { "query", "fragment", "form_post" }, - request_parameter_supported = true, - request_uri_parameter_supported = true, - require_request_uri_registration = false - }); - } - - [HttpGet("/.well-known/jwks")] - public IActionResult GetJwks() - { - using var rsa = options.Value.GetRsaPublicKey(); - if (rsa == null) - { - return BadRequest("Public key is not configured"); - } - - var parameters = rsa.ExportParameters(false); - var keyId = Convert.ToBase64String(SHA256.HashData(parameters.Modulus!)[..8]) - .Replace("+", "-") - .Replace("/", "_") - .Replace("=", ""); - - return Ok(new - { - keys = new[] - { - new - { - kty = "RSA", - use = "sig", - kid = keyId, - n = Base64UrlEncoder.Encode(parameters.Modulus!), - e = Base64UrlEncoder.Encode(parameters.Exponent!), - alg = "RS256" - } - } - }); - } -} - -public class TokenRequest -{ - [JsonPropertyName("grant_type")] - [FromForm(Name = "grant_type")] - public string? GrantType { get; set; } - - [JsonPropertyName("code")] - [FromForm(Name = "code")] - public string? Code { get; set; } - - [JsonPropertyName("redirect_uri")] - [FromForm(Name = "redirect_uri")] - public string? RedirectUri { get; set; } - - [JsonPropertyName("client_id")] - [FromForm(Name = "client_id")] - public Guid? ClientId { get; set; } - - [JsonPropertyName("client_secret")] - [FromForm(Name = "client_secret")] - public string? ClientSecret { get; set; } - - [JsonPropertyName("refresh_token")] - [FromForm(Name = "refresh_token")] - public string? RefreshToken { get; set; } - - [JsonPropertyName("scope")] - [FromForm(Name = "scope")] - public string? Scope { get; set; } - - [JsonPropertyName("code_verifier")] - [FromForm(Name = "code_verifier")] - public string? CodeVerifier { get; set; } -} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Auth/OidcProvider/Models/AuthorizationCodeInfo.cs b/DysonNetwork.Sphere/Auth/OidcProvider/Models/AuthorizationCodeInfo.cs deleted file mode 100644 index d043cab..0000000 --- a/DysonNetwork.Sphere/Auth/OidcProvider/Models/AuthorizationCodeInfo.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using System.Collections.Generic; -using NodaTime; - -namespace DysonNetwork.Sphere.Auth.OidcProvider.Models; - -public class AuthorizationCodeInfo -{ - public Guid ClientId { get; set; } - public Guid AccountId { get; set; } - public string RedirectUri { get; set; } = string.Empty; - public List Scopes { get; set; } = new(); - public string? CodeChallenge { get; set; } - public string? CodeChallengeMethod { get; set; } - public string? Nonce { get; set; } - public Instant CreatedAt { get; set; } -} diff --git a/DysonNetwork.Sphere/Auth/OidcProvider/Options/OidcProviderOptions.cs b/DysonNetwork.Sphere/Auth/OidcProvider/Options/OidcProviderOptions.cs deleted file mode 100644 index 6d57cb3..0000000 --- a/DysonNetwork.Sphere/Auth/OidcProvider/Options/OidcProviderOptions.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Security.Cryptography; - -namespace DysonNetwork.Sphere.Auth.OidcProvider.Options; - -public class OidcProviderOptions -{ - public string IssuerUri { get; set; } = "https://your-issuer-uri.com"; - public string? PublicKeyPath { get; set; } - public string? PrivateKeyPath { get; set; } - public TimeSpan AccessTokenLifetime { get; set; } = TimeSpan.FromHours(1); - public TimeSpan RefreshTokenLifetime { get; set; } = TimeSpan.FromDays(30); - public TimeSpan AuthorizationCodeLifetime { get; set; } = TimeSpan.FromMinutes(5); - public bool RequireHttpsMetadata { get; set; } = true; - - public RSA? GetRsaPrivateKey() - { - if (string.IsNullOrEmpty(PrivateKeyPath) || !File.Exists(PrivateKeyPath)) - return null; - - var privateKey = File.ReadAllText(PrivateKeyPath); - var rsa = RSA.Create(); - rsa.ImportFromPem(privateKey.AsSpan()); - return rsa; - } - - public RSA? GetRsaPublicKey() - { - if (string.IsNullOrEmpty(PublicKeyPath) || !File.Exists(PublicKeyPath)) - return null; - - var publicKey = File.ReadAllText(PublicKeyPath); - var rsa = RSA.Create(); - rsa.ImportFromPem(publicKey.AsSpan()); - return rsa; - } -} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Auth/OidcProvider/Responses/AuthorizationResponse.cs b/DysonNetwork.Sphere/Auth/OidcProvider/Responses/AuthorizationResponse.cs deleted file mode 100644 index 45cf25c..0000000 --- a/DysonNetwork.Sphere/Auth/OidcProvider/Responses/AuthorizationResponse.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System.Text.Json.Serialization; - -namespace DysonNetwork.Sphere.Auth.OidcProvider.Responses; - -public class AuthorizationResponse -{ - [JsonPropertyName("code")] - public string Code { get; set; } = null!; - - [JsonPropertyName("state")] - public string? State { get; set; } - - [JsonPropertyName("scope")] - public string? Scope { get; set; } - - - [JsonPropertyName("session_state")] - public string? SessionState { get; set; } - - - [JsonPropertyName("iss")] - public string? Issuer { get; set; } -} diff --git a/DysonNetwork.Sphere/Auth/OidcProvider/Responses/ErrorResponse.cs b/DysonNetwork.Sphere/Auth/OidcProvider/Responses/ErrorResponse.cs deleted file mode 100644 index 0018a47..0000000 --- a/DysonNetwork.Sphere/Auth/OidcProvider/Responses/ErrorResponse.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System.Text.Json.Serialization; - -namespace DysonNetwork.Sphere.Auth.OidcProvider.Responses; - -public class ErrorResponse -{ - [JsonPropertyName("error")] - public string Error { get; set; } = null!; - - [JsonPropertyName("error_description")] - public string? ErrorDescription { get; set; } - - - [JsonPropertyName("error_uri")] - public string? ErrorUri { get; set; } - - - [JsonPropertyName("state")] - public string? State { get; set; } -} diff --git a/DysonNetwork.Sphere/Auth/OidcProvider/Responses/TokenResponse.cs b/DysonNetwork.Sphere/Auth/OidcProvider/Responses/TokenResponse.cs deleted file mode 100644 index 6d41cf4..0000000 --- a/DysonNetwork.Sphere/Auth/OidcProvider/Responses/TokenResponse.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.Text.Json.Serialization; - -namespace DysonNetwork.Sphere.Auth.OidcProvider.Responses; - -public class TokenResponse -{ - [JsonPropertyName("access_token")] - public string AccessToken { get; set; } = null!; - - [JsonPropertyName("expires_in")] - public int ExpiresIn { get; set; } - - [JsonPropertyName("token_type")] - public string TokenType { get; set; } = "Bearer"; - - [JsonPropertyName("refresh_token")] - public string? RefreshToken { get; set; } - - - [JsonPropertyName("scope")] - public string? Scope { get; set; } - - - [JsonPropertyName("id_token")] - public string? IdToken { get; set; } -} diff --git a/DysonNetwork.Sphere/Auth/OidcProvider/Services/OidcProviderService.cs b/DysonNetwork.Sphere/Auth/OidcProvider/Services/OidcProviderService.cs deleted file mode 100644 index 4345dab..0000000 --- a/DysonNetwork.Sphere/Auth/OidcProvider/Services/OidcProviderService.cs +++ /dev/null @@ -1,395 +0,0 @@ -using System.IdentityModel.Tokens.Jwt; -using System.Security.Claims; -using System.Security.Cryptography; -using System.Text; -using DysonNetwork.Sphere.Auth.OidcProvider.Models; -using DysonNetwork.Sphere.Auth.OidcProvider.Options; -using DysonNetwork.Sphere.Auth.OidcProvider.Responses; -using DysonNetwork.Sphere.Developer; -using DysonNetwork.Sphere.Storage; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Options; -using Microsoft.IdentityModel.Tokens; -using NodaTime; - -namespace DysonNetwork.Sphere.Auth.OidcProvider.Services; - -public class OidcProviderService( - AppDatabase db, - AuthService auth, - ICacheService cache, - IOptions options, - ILogger logger -) -{ - private readonly OidcProviderOptions _options = options.Value; - - public async Task FindClientByIdAsync(Guid clientId) - { - return await db.CustomApps - .Include(c => c.Secrets) - .FirstOrDefaultAsync(c => c.Id == clientId); - } - - public async Task FindClientByAppIdAsync(Guid appId) - { - return await db.CustomApps - .Include(c => c.Secrets) - .FirstOrDefaultAsync(c => c.Id == appId); - } - - public async Task FindValidSessionAsync(Guid accountId, Guid clientId) - { - var now = SystemClock.Instance.GetCurrentInstant(); - - return await db.AuthSessions - .Include(s => s.Challenge) - .Where(s => s.AccountId == accountId && - s.AppId == clientId && - (s.ExpiredAt == null || s.ExpiredAt > now) && - s.Challenge.Type == ChallengeType.OAuth) - .OrderByDescending(s => s.CreatedAt) - .FirstOrDefaultAsync(); - } - - public async Task ValidateClientCredentialsAsync(Guid clientId, string clientSecret) - { - var client = await FindClientByIdAsync(clientId); - if (client == null) return false; - - var clock = SystemClock.Instance; - var secret = client.Secrets - .Where(s => s.IsOidc && (s.ExpiredAt == null || s.ExpiredAt > clock.GetCurrentInstant())) - .FirstOrDefault(s => s.Secret == clientSecret); // In production, use proper hashing - - return secret != null; - } - - public async Task GenerateTokenResponseAsync( - Guid clientId, - string? authorizationCode = null, - string? redirectUri = null, - string? codeVerifier = null, - Guid? sessionId = null - ) - { - var client = await FindClientByIdAsync(clientId); - if (client == null) - throw new InvalidOperationException("Client not found"); - - Session session; - var clock = SystemClock.Instance; - var now = clock.GetCurrentInstant(); - - List? scopes = null; - if (authorizationCode != null) - { - // Authorization code flow - var authCode = await ValidateAuthorizationCodeAsync(authorizationCode, clientId, redirectUri, codeVerifier); - if (authCode is null) throw new InvalidOperationException("Invalid authorization code"); - var account = await db.Accounts.Where(a => a.Id == authCode.AccountId).FirstOrDefaultAsync(); - if (account is null) throw new InvalidOperationException("Account was not found"); - - session = await auth.CreateSessionForOidcAsync(account, now, client.Id); - scopes = authCode.Scopes; - } - else if (sessionId.HasValue) - { - // Refresh token flow - session = await FindSessionByIdAsync(sessionId.Value) ?? - throw new InvalidOperationException("Invalid session"); - - // Verify the session is still valid - if (session.ExpiredAt < now) - throw new InvalidOperationException("Session has expired"); - } - else - { - throw new InvalidOperationException("Either authorization code or session ID must be provided"); - } - - var expiresIn = (int)_options.AccessTokenLifetime.TotalSeconds; - var expiresAt = now.Plus(Duration.FromSeconds(expiresIn)); - - // Generate an access token - var accessToken = GenerateJwtToken(client, session, expiresAt, scopes); - var refreshToken = GenerateRefreshToken(session); - - return new TokenResponse - { - AccessToken = accessToken, - ExpiresIn = expiresIn, - TokenType = "Bearer", - RefreshToken = refreshToken, - Scope = scopes != null ? string.Join(" ", scopes) : null - }; - } - - private string GenerateJwtToken( - CustomApp client, - Session session, - Instant expiresAt, - IEnumerable? scopes = null - ) - { - var tokenHandler = new JwtSecurityTokenHandler(); - var clock = SystemClock.Instance; - var now = clock.GetCurrentInstant(); - - var tokenDescriptor = new SecurityTokenDescriptor - { - Subject = new ClaimsIdentity([ - new Claim(JwtRegisteredClaimNames.Sub, session.AccountId.ToString()), - new Claim(JwtRegisteredClaimNames.Jti, session.Id.ToString()), - new Claim(JwtRegisteredClaimNames.Iat, now.ToUnixTimeSeconds().ToString(), - ClaimValueTypes.Integer64), - new Claim("client_id", client.Id.ToString()) - ]), - Expires = expiresAt.ToDateTimeUtc(), - Issuer = _options.IssuerUri, - Audience = client.Id.ToString() - }; - - // Try to use RSA signing if keys are available, fall back to HMAC - var rsaPrivateKey = _options.GetRsaPrivateKey(); - tokenDescriptor.SigningCredentials = new SigningCredentials( - new RsaSecurityKey(rsaPrivateKey), - SecurityAlgorithms.RsaSha256 - ); - - // Add scopes as claims if provided - var effectiveScopes = scopes?.ToList() ?? client.OauthConfig!.AllowedScopes?.ToList() ?? []; - if (effectiveScopes.Count != 0) - { - tokenDescriptor.Subject.AddClaims( - effectiveScopes.Select(scope => new Claim("scope", scope))); - } - - var token = tokenHandler.CreateToken(tokenDescriptor); - return tokenHandler.WriteToken(token); - } - - public (bool isValid, JwtSecurityToken? token) ValidateToken(string token) - { - try - { - var tokenHandler = new JwtSecurityTokenHandler(); - var validationParameters = new TokenValidationParameters - { - ValidateIssuer = true, - ValidIssuer = _options.IssuerUri, - ValidateAudience = false, - ValidateLifetime = true, - ClockSkew = TimeSpan.Zero - }; - - // Try to use RSA validation if public key is available - var rsaPublicKey = _options.GetRsaPublicKey(); - validationParameters.IssuerSigningKey = new RsaSecurityKey(rsaPublicKey); - validationParameters.ValidateIssuerSigningKey = true; - validationParameters.ValidAlgorithms = new[] { SecurityAlgorithms.RsaSha256 }; - - - tokenHandler.ValidateToken(token, validationParameters, out var validatedToken); - return (true, (JwtSecurityToken)validatedToken); - } - catch (Exception ex) - { - logger.LogError(ex, "Token validation failed"); - return (false, null); - } - } - - public async Task FindSessionByIdAsync(Guid sessionId) - { - return await db.AuthSessions - .Include(s => s.Account) - .Include(s => s.Challenge) - .Include(s => s.App) - .FirstOrDefaultAsync(s => s.Id == sessionId); - } - - private static string GenerateRefreshToken(Session session) - { - return Convert.ToBase64String(session.Id.ToByteArray()); - } - - private static bool VerifyHashedSecret(string secret, string hashedSecret) - { - // In a real implementation, you'd use a proper password hashing algorithm like PBKDF2, bcrypt, or Argon2 - // For now, we'll do a simple comparison, but you should replace this with proper hashing - return string.Equals(secret, hashedSecret, StringComparison.Ordinal); - } - - public async Task GenerateAuthorizationCodeForReuseSessionAsync( - Session session, - Guid clientId, - string redirectUri, - IEnumerable scopes, - string? codeChallenge = null, - string? codeChallengeMethod = null, - string? nonce = null) - { - var clock = SystemClock.Instance; - var now = clock.GetCurrentInstant(); - var code = Guid.NewGuid().ToString("N"); - - // Update the session's last activity time - await db.AuthSessions.Where(s => s.Id == session.Id) - .ExecuteUpdateAsync(s => s.SetProperty(s => s.LastGrantedAt, now)); - - // Create the authorization code info - var authCodeInfo = new AuthorizationCodeInfo - { - ClientId = clientId, - AccountId = session.AccountId, - RedirectUri = redirectUri, - Scopes = scopes.ToList(), - CodeChallenge = codeChallenge, - CodeChallengeMethod = codeChallengeMethod, - Nonce = nonce, - CreatedAt = now - }; - - // Store the code with its metadata in the cache - var cacheKey = $"auth:code:{code}"; - await cache.SetAsync(cacheKey, authCodeInfo, _options.AuthorizationCodeLifetime); - - logger.LogInformation("Generated authorization code for client {ClientId} and user {UserId}", clientId, session.AccountId); - return code; - } - - public async Task GenerateAuthorizationCodeAsync( - Guid clientId, - Guid userId, - string redirectUri, - IEnumerable scopes, - string? codeChallenge = null, - string? codeChallengeMethod = null, - string? nonce = null - ) - { - // Generate a random code - var clock = SystemClock.Instance; - var code = GenerateRandomString(32); - var now = clock.GetCurrentInstant(); - - // Create the authorization code info - var authCodeInfo = new AuthorizationCodeInfo - { - ClientId = clientId, - AccountId = userId, - RedirectUri = redirectUri, - Scopes = scopes.ToList(), - CodeChallenge = codeChallenge, - CodeChallengeMethod = codeChallengeMethod, - Nonce = nonce, - CreatedAt = now - }; - - // Store the code with its metadata in the cache - var cacheKey = $"auth:code:{code}"; - await cache.SetAsync(cacheKey, authCodeInfo, _options.AuthorizationCodeLifetime); - - logger.LogInformation("Generated authorization code for client {ClientId} and user {UserId}", clientId, userId); - return code; - } - - private async Task ValidateAuthorizationCodeAsync( - string code, - Guid clientId, - string? redirectUri = null, - string? codeVerifier = null - ) - { - var cacheKey = $"auth:code:{code}"; - var (found, authCode) = await cache.GetAsyncWithStatus(cacheKey); - - if (!found || authCode == null) - { - logger.LogWarning("Authorization code not found: {Code}", code); - return null; - } - - // Verify client ID matches - if (authCode.ClientId != clientId) - { - logger.LogWarning( - "Client ID mismatch for code {Code}. Expected: {ExpectedClientId}, Actual: {ActualClientId}", - code, authCode.ClientId, clientId); - return null; - } - - // Verify redirect URI if provided - if (!string.IsNullOrEmpty(redirectUri) && authCode.RedirectUri != redirectUri) - { - logger.LogWarning("Redirect URI mismatch for code {Code}", code); - return null; - } - - // Verify PKCE code challenge if one was provided during authorization - if (!string.IsNullOrEmpty(authCode.CodeChallenge)) - { - if (string.IsNullOrEmpty(codeVerifier)) - { - logger.LogWarning("PKCE code verifier is required but not provided for code {Code}", code); - return null; - } - - var isValid = authCode.CodeChallengeMethod?.ToUpperInvariant() switch - { - "S256" => VerifyCodeChallenge(codeVerifier, authCode.CodeChallenge, "S256"), - "PLAIN" => VerifyCodeChallenge(codeVerifier, authCode.CodeChallenge, "PLAIN"), - _ => false // Unsupported code challenge method - }; - - if (!isValid) - { - logger.LogWarning("PKCE code verifier validation failed for code {Code}", code); - return null; - } - } - - // Code is valid, remove it from the cache (codes are single-use) - await cache.RemoveAsync(cacheKey); - - return authCode; - } - - private static string GenerateRandomString(int length) - { - const string chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~"; - var random = RandomNumberGenerator.Create(); - var result = new char[length]; - - for (int i = 0; i < length; i++) - { - var randomNumber = new byte[4]; - random.GetBytes(randomNumber); - var index = (int)(BitConverter.ToUInt32(randomNumber, 0) % chars.Length); - result[i] = chars[index]; - } - - return new string(result); - } - - private static bool VerifyCodeChallenge(string codeVerifier, string codeChallenge, string method) - { - if (string.IsNullOrEmpty(codeVerifier)) return false; - - if (method == "S256") - { - using var sha256 = SHA256.Create(); - var hash = sha256.ComputeHash(Encoding.UTF8.GetBytes(codeVerifier)); - var base64 = Base64UrlEncoder.Encode(hash); - return string.Equals(base64, codeChallenge, StringComparison.Ordinal); - } - - if (method == "PLAIN") - { - return string.Equals(codeVerifier, codeChallenge, StringComparison.Ordinal); - } - - return false; - } -} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Auth/OpenId/AfdianOidcService.cs b/DysonNetwork.Sphere/Auth/OpenId/AfdianOidcService.cs deleted file mode 100644 index c0e0600..0000000 --- a/DysonNetwork.Sphere/Auth/OpenId/AfdianOidcService.cs +++ /dev/null @@ -1,94 +0,0 @@ -using System.Net.Http.Json; -using System.Text.Json; -using DysonNetwork.Sphere.Storage; - -namespace DysonNetwork.Sphere.Auth.OpenId; - -public class AfdianOidcService( - IConfiguration configuration, - IHttpClientFactory httpClientFactory, - AppDatabase db, - AuthService auth, - ICacheService cache, - ILogger logger -) - : OidcService(configuration, httpClientFactory, db, auth, cache) -{ - public override string ProviderName => "Afdian"; - protected override string DiscoveryEndpoint => ""; // Afdian doesn't have a standard OIDC discovery endpoint - protected override string ConfigSectionName => "Afdian"; - - public override string GetAuthorizationUrl(string state, string nonce) - { - var config = GetProviderConfig(); - var queryParams = new Dictionary - { - { "client_id", config.ClientId }, - { "redirect_uri", config.RedirectUri }, - { "response_type", "code" }, - { "scope", "basic" }, - { "state", state }, - }; - - var queryString = string.Join("&", queryParams.Select(p => $"{p.Key}={Uri.EscapeDataString(p.Value)}")); - return $"https://afdian.com/oauth2/authorize?{queryString}"; - } - - protected override Task GetDiscoveryDocumentAsync() - { - return Task.FromResult(new OidcDiscoveryDocument - { - AuthorizationEndpoint = "https://afdian.com/oauth2/authorize", - TokenEndpoint = "https://afdian.com/oauth2/access_token", - UserinfoEndpoint = null, - JwksUri = null - })!; - } - - public override async Task ProcessCallbackAsync(OidcCallbackData callbackData) - { - try - { - var config = GetProviderConfig(); - var content = new FormUrlEncodedContent(new Dictionary - { - { "client_id", config.ClientId }, - { "client_secret", config.ClientSecret }, - { "grant_type", "authorization_code" }, - { "code", callbackData.Code }, - { "redirect_uri", config.RedirectUri }, - }); - - var client = HttpClientFactory.CreateClient(); - var request = new HttpRequestMessage(HttpMethod.Post, "https://afdian.com/oauth2/access_token"); - request.Content = content; - - var response = await client.SendAsync(request); - response.EnsureSuccessStatusCode(); - - var json = await response.Content.ReadAsStringAsync(); - logger.LogInformation("Trying get userinfo from afdian, response: {Response}", json); - var afdianResponse = JsonDocument.Parse(json).RootElement; - - var user = afdianResponse.TryGetProperty("data", out var dataElement) ? dataElement : default; - var userId = user.TryGetProperty("user_id", out var userIdElement) ? userIdElement.GetString() ?? "" : ""; - var avatar = user.TryGetProperty("avatar", out var avatarElement) ? avatarElement.GetString() : null; - - return new OidcUserInfo - { - UserId = userId, - DisplayName = (user.TryGetProperty("name", out var nameElement) - ? nameElement.GetString() - : null) ?? "", - ProfilePictureUrl = avatar, - Provider = ProviderName - }; - } - catch (Exception ex) - { - // Due to afidan's API isn't compliant with OAuth2, we want more logs from it to investigate. - logger.LogError(ex, "Failed to get user info from Afdian"); - throw; - } - } -} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Auth/OpenId/AppleMobileSignInRequest.cs b/DysonNetwork.Sphere/Auth/OpenId/AppleMobileSignInRequest.cs deleted file mode 100644 index f5249cd..0000000 --- a/DysonNetwork.Sphere/Auth/OpenId/AppleMobileSignInRequest.cs +++ /dev/null @@ -1,19 +0,0 @@ - -using System.ComponentModel.DataAnnotations; -using System.Text.Json.Serialization; - -namespace DysonNetwork.Sphere.Auth.OpenId; - -public class AppleMobileConnectRequest -{ - [Required] - public required string IdentityToken { get; set; } - [Required] - public required string AuthorizationCode { get; set; } -} - -public class AppleMobileSignInRequest : AppleMobileConnectRequest -{ - [Required] - public required string DeviceId { get; set; } -} diff --git a/DysonNetwork.Sphere/Auth/OpenId/AppleOidcService.cs b/DysonNetwork.Sphere/Auth/OpenId/AppleOidcService.cs deleted file mode 100644 index 75420b8..0000000 --- a/DysonNetwork.Sphere/Auth/OpenId/AppleOidcService.cs +++ /dev/null @@ -1,279 +0,0 @@ -using System.IdentityModel.Tokens.Jwt; -using System.Security.Cryptography; -using System.Text; -using System.Text.Json; -using System.Text.Json.Serialization; -using DysonNetwork.Sphere.Storage; -using Microsoft.IdentityModel.Tokens; - -namespace DysonNetwork.Sphere.Auth.OpenId; - -/// -/// Implementation of OpenID Connect service for Apple Sign In -/// -public class AppleOidcService( - IConfiguration configuration, - IHttpClientFactory httpClientFactory, - AppDatabase db, - AuthService auth, - ICacheService cache -) - : OidcService(configuration, httpClientFactory, db, auth, cache) -{ - private readonly IConfiguration _configuration = configuration; - private readonly IHttpClientFactory _httpClientFactory = httpClientFactory; - - public override string ProviderName => "apple"; - protected override string DiscoveryEndpoint => "https://appleid.apple.com/.well-known/openid-configuration"; - protected override string ConfigSectionName => "Apple"; - - public override string GetAuthorizationUrl(string state, string nonce) - { - var config = GetProviderConfig(); - - var queryParams = new Dictionary - { - { "client_id", config.ClientId }, - { "redirect_uri", config.RedirectUri }, - { "response_type", "code id_token" }, - { "scope", "name email" }, - { "response_mode", "form_post" }, - { "state", state }, - { "nonce", nonce } - }; - - var queryString = string.Join("&", queryParams.Select(p => $"{p.Key}={Uri.EscapeDataString(p.Value)}")); - return $"https://appleid.apple.com/auth/authorize?{queryString}"; - } - - public override async Task ProcessCallbackAsync(OidcCallbackData callbackData) - { - // Verify and decode the id_token - var userInfo = await ValidateTokenAsync(callbackData.IdToken); - - // If user data is provided in first login, parse it - if (!string.IsNullOrEmpty(callbackData.RawData)) - { - var userData = JsonSerializer.Deserialize(callbackData.RawData); - if (userData?.Name != null) - { - userInfo.FirstName = userData.Name.FirstName ?? ""; - userInfo.LastName = userData.Name.LastName ?? ""; - userInfo.DisplayName = $"{userInfo.FirstName} {userInfo.LastName}".Trim(); - } - } - - // Exchange authorization code for access token (optional, if you need the access token) - if (string.IsNullOrEmpty(callbackData.Code)) return userInfo; - var tokenResponse = await ExchangeCodeForTokensAsync(callbackData.Code); - if (tokenResponse == null) return userInfo; - userInfo.AccessToken = tokenResponse.AccessToken; - userInfo.RefreshToken = tokenResponse.RefreshToken; - - return userInfo; - } - - private async Task ValidateTokenAsync(string idToken) - { - // Get Apple's public keys - var jwksJson = await GetAppleJwksAsync(); - var jwks = JsonSerializer.Deserialize(jwksJson) ?? new AppleJwks { Keys = new List() }; - - // Parse the JWT header to get the key ID - var handler = new JwtSecurityTokenHandler(); - var jwtToken = handler.ReadJwtToken(idToken); - var kid = jwtToken.Header.Kid; - - // Find the matching key - var key = jwks.Keys.FirstOrDefault(k => k.Kid == kid); - if (key == null) - { - throw new SecurityTokenValidationException("Unable to find matching key in Apple's JWKS"); - } - - // Create the validation parameters - var validationParameters = new TokenValidationParameters - { - ValidateIssuer = true, - ValidIssuer = "https://appleid.apple.com", - ValidateAudience = true, - ValidAudience = GetProviderConfig().ClientId, - ValidateLifetime = true, - IssuerSigningKey = key.ToSecurityKey() - }; - - return ValidateAndExtractIdToken(idToken, validationParameters); - } - - protected override Dictionary BuildTokenRequestParameters( - string code, - ProviderConfiguration config, - string? codeVerifier - ) - { - var parameters = new Dictionary - { - { "client_id", config.ClientId }, - { "client_secret", GenerateClientSecret() }, - { "code", code }, - { "grant_type", "authorization_code" }, - { "redirect_uri", config.RedirectUri } - }; - - return parameters; - } - - private async Task GetAppleJwksAsync() - { - var client = _httpClientFactory.CreateClient(); - var response = await client.GetAsync("https://appleid.apple.com/auth/keys"); - response.EnsureSuccessStatusCode(); - - return await response.Content.ReadAsStringAsync(); - } - - /// - /// Generates a client secret for Apple Sign In using JWT - /// - private string GenerateClientSecret() - { - var now = DateTime.UtcNow; - var teamId = _configuration["Oidc:Apple:TeamId"]; - var clientId = _configuration["Oidc:Apple:ClientId"]; - var keyId = _configuration["Oidc:Apple:KeyId"]; - var privateKeyPath = _configuration["Oidc:Apple:PrivateKeyPath"]; - - if (string.IsNullOrEmpty(teamId) || string.IsNullOrEmpty(clientId) || string.IsNullOrEmpty(keyId) || - string.IsNullOrEmpty(privateKeyPath)) - { - throw new InvalidOperationException("Apple OIDC configuration is missing required values (TeamId, ClientId, KeyId, PrivateKeyPath)."); - } - - // Read the private key - var privateKey = File.ReadAllText(privateKeyPath); - - // Create the JWT header - var header = new Dictionary - { - { "alg", "ES256" }, - { "kid", keyId } - }; - - // Create the JWT payload - var payload = new Dictionary - { - { "iss", teamId }, - { "iat", ToUnixTimeSeconds(now) }, - { "exp", ToUnixTimeSeconds(now.AddMinutes(5)) }, - { "aud", "https://appleid.apple.com" }, - { "sub", clientId } - }; - - // Convert header and payload to Base64Url - var headerJson = JsonSerializer.Serialize(header); - var payloadJson = JsonSerializer.Serialize(payload); - var headerBase64 = Base64UrlEncode(Encoding.UTF8.GetBytes(headerJson)); - var payloadBase64 = Base64UrlEncode(Encoding.UTF8.GetBytes(payloadJson)); - - // Create the signature - var dataToSign = $"{headerBase64}.{payloadBase64}"; - var signature = SignWithECDsa(dataToSign, privateKey); - - // Combine all parts - return $"{headerBase64}.{payloadBase64}.{signature}"; - } - - private long ToUnixTimeSeconds(DateTime dateTime) - { - return new DateTimeOffset(dateTime).ToUnixTimeSeconds(); - } - - private string SignWithECDsa(string dataToSign, string privateKey) - { - using var ecdsa = ECDsa.Create(); - ecdsa.ImportFromPem(privateKey); - - var bytes = Encoding.UTF8.GetBytes(dataToSign); - var signature = ecdsa.SignData(bytes, HashAlgorithmName.SHA256); - - return Base64UrlEncode(signature); - } - - private string Base64UrlEncode(byte[] data) - { - return Convert.ToBase64String(data) - .Replace('+', '-') - .Replace('/', '_') - .TrimEnd('='); - } -} - -public class AppleUserData -{ - [JsonPropertyName("name")] public AppleNameData? Name { get; set; } - - [JsonPropertyName("email")] public string? Email { get; set; } -} - -public class AppleNameData -{ - [JsonPropertyName("firstName")] public string? FirstName { get; set; } - - [JsonPropertyName("lastName")] public string? LastName { get; set; } -} - -public class AppleJwks -{ - [JsonPropertyName("keys")] public List Keys { get; set; } = new List(); -} - -public class AppleKey -{ - [JsonPropertyName("kty")] public string? Kty { get; set; } - - [JsonPropertyName("kid")] public string? Kid { get; set; } - - [JsonPropertyName("use")] public string? Use { get; set; } - - [JsonPropertyName("alg")] public string? Alg { get; set; } - - [JsonPropertyName("n")] public string? N { get; set; } - - [JsonPropertyName("e")] public string? E { get; set; } - - public SecurityKey ToSecurityKey() - { - if (Kty != "RSA" || string.IsNullOrEmpty(N) || string.IsNullOrEmpty(E)) - { - throw new InvalidOperationException("Invalid key data"); - } - - var parameters = new RSAParameters - { - Modulus = Base64UrlDecode(N), - Exponent = Base64UrlDecode(E) - }; - - var rsa = RSA.Create(); - rsa.ImportParameters(parameters); - - return new RsaSecurityKey(rsa); - } - - private byte[] Base64UrlDecode(string input) - { - var output = input - .Replace('-', '+') - .Replace('_', '/'); - - switch (output.Length % 4) - { - case 0: break; - case 2: output += "=="; break; - case 3: output += "="; break; - default: throw new InvalidOperationException("Invalid base64url string"); - } - - return Convert.FromBase64String(output); - } -} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Auth/OpenId/ConnectionController.cs b/DysonNetwork.Sphere/Auth/OpenId/ConnectionController.cs deleted file mode 100644 index 49f11ee..0000000 --- a/DysonNetwork.Sphere/Auth/OpenId/ConnectionController.cs +++ /dev/null @@ -1,409 +0,0 @@ -using DysonNetwork.Sphere.Account; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; -using DysonNetwork.Sphere.Storage; -using NodaTime; - -namespace DysonNetwork.Sphere.Auth.OpenId; - -[ApiController] -[Route("/api/accounts/me/connections")] -[Authorize] -public class ConnectionController( - AppDatabase db, - IEnumerable oidcServices, - AccountService accounts, - AuthService auth, - ICacheService cache -) : ControllerBase -{ - private const string StateCachePrefix = "oidc-state:"; - private const string ReturnUrlCachePrefix = "oidc-returning:"; - private static readonly TimeSpan StateExpiration = TimeSpan.FromMinutes(15); - - [HttpGet] - public async Task>> GetConnections() - { - if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) - return Unauthorized(); - - var connections = await db.AccountConnections - .Where(c => c.AccountId == currentUser.Id) - .Select(c => new - { - c.Id, - c.AccountId, - c.Provider, - c.ProvidedIdentifier, - c.Meta, - c.LastUsedAt, - c.CreatedAt, - c.UpdatedAt, - }) - .ToListAsync(); - return Ok(connections); - } - - [HttpDelete("{id:guid}")] - public async Task RemoveConnection(Guid id) - { - if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) - return Unauthorized(); - - var connection = await db.AccountConnections - .Where(c => c.Id == id && c.AccountId == currentUser.Id) - .FirstOrDefaultAsync(); - if (connection == null) - return NotFound(); - - db.AccountConnections.Remove(connection); - await db.SaveChangesAsync(); - - return Ok(); - } - - [HttpPost("/auth/connect/apple/mobile")] - public async Task ConnectAppleMobile([FromBody] AppleMobileConnectRequest request) - { - if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) - return Unauthorized(); - - if (GetOidcService("apple") is not AppleOidcService appleService) - return StatusCode(503, "Apple OIDC service not available"); - - var callbackData = new OidcCallbackData - { - IdToken = request.IdentityToken, - Code = request.AuthorizationCode, - }; - - OidcUserInfo userInfo; - try - { - userInfo = await appleService.ProcessCallbackAsync(callbackData); - } - catch (Exception ex) - { - return BadRequest($"Error processing Apple token: {ex.Message}"); - } - - var existingConnection = await db.AccountConnections - .FirstOrDefaultAsync(c => - c.Provider == "apple" && - c.ProvidedIdentifier == userInfo.UserId); - - if (existingConnection != null) - { - return BadRequest( - $"This Apple account is already linked to {(existingConnection.AccountId == currentUser.Id ? "your account" : "another user")}."); - } - - db.AccountConnections.Add(new AccountConnection - { - AccountId = currentUser.Id, - Provider = "apple", - ProvidedIdentifier = userInfo.UserId!, - AccessToken = userInfo.AccessToken, - RefreshToken = userInfo.RefreshToken, - LastUsedAt = SystemClock.Instance.GetCurrentInstant(), - Meta = userInfo.ToMetadata(), - }); - - await db.SaveChangesAsync(); - - return Ok(new { message = "Successfully connected Apple account." }); - } - - private OidcService? GetOidcService(string provider) - { - return oidcServices.FirstOrDefault(s => s.ProviderName.Equals(provider, StringComparison.OrdinalIgnoreCase)); - } - - public class ConnectProviderRequest - { - public string Provider { get; set; } = null!; - public string? ReturnUrl { get; set; } - } - - /// - /// Initiates manual connection to an OAuth provider for the current user - /// - [HttpPost("connect")] - public async Task> InitiateConnection([FromBody] ConnectProviderRequest request) - { - if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) - return Unauthorized(); - - var oidcService = GetOidcService(request.Provider); - if (oidcService == null) - return BadRequest($"Provider '{request.Provider}' is not supported"); - - var existingConnection = await db.AccountConnections - .AnyAsync(c => c.AccountId == currentUser.Id && c.Provider == oidcService.ProviderName); - - if (existingConnection) - return BadRequest($"You already have a {request.Provider} connection"); - - var state = Guid.NewGuid().ToString("N"); - var nonce = Guid.NewGuid().ToString("N"); - var stateValue = $"{currentUser.Id}|{request.Provider}|{nonce}"; - var finalReturnUrl = !string.IsNullOrEmpty(request.ReturnUrl) ? request.ReturnUrl : "/settings/connections"; - - // Store state and return URL in cache - await cache.SetAsync($"{StateCachePrefix}{state}", stateValue, StateExpiration); - await cache.SetAsync($"{ReturnUrlCachePrefix}{state}", finalReturnUrl, StateExpiration); - - var authUrl = oidcService.GetAuthorizationUrl(state, nonce); - - return Ok(new - { - authUrl, - message = $"Redirect to this URL to connect your {request.Provider} account" - }); - } - - [AllowAnonymous] - [Route("/api/auth/callback/{provider}")] - [HttpGet, HttpPost] - public async Task HandleCallback([FromRoute] string provider) - { - var oidcService = GetOidcService(provider); - if (oidcService == null) - return BadRequest($"Provider '{provider}' is not supported."); - - var callbackData = await ExtractCallbackData(Request); - if (callbackData.State == null) - return BadRequest("State parameter is missing."); - - // Get the state from the cache - var stateKey = $"{StateCachePrefix}{callbackData.State}"; - - // Try to get the state as OidcState first (new format) - var oidcState = await cache.GetAsync(stateKey); - - // If not found, try to get as string (legacy format) - if (oidcState == null) - { - var stateValue = await cache.GetAsync(stateKey); - if (string.IsNullOrEmpty(stateValue) || !OidcState.TryParse(stateValue, out oidcState) || oidcState == null) - return BadRequest("Invalid or expired state parameter"); - } - - // Remove the state from cache to prevent replay attacks - await cache.RemoveAsync(stateKey); - - // Handle the flow based on state type - if (oidcState.FlowType == OidcFlowType.Connect && oidcState.AccountId.HasValue) - { - // Connection flow - if (oidcState.DeviceId != null) - { - callbackData.State = oidcState.DeviceId; - } - return await HandleManualConnection(provider, oidcService, callbackData, oidcState.AccountId.Value); - } - else if (oidcState.FlowType == OidcFlowType.Login) - { - // Login/Registration flow - if (!string.IsNullOrEmpty(oidcState.DeviceId)) - { - callbackData.State = oidcState.DeviceId; - } - - // Store return URL if provided - if (!string.IsNullOrEmpty(oidcState.ReturnUrl) && oidcState.ReturnUrl != "/") - { - var returnUrlKey = $"{ReturnUrlCachePrefix}{callbackData.State}"; - await cache.SetAsync(returnUrlKey, oidcState.ReturnUrl, StateExpiration); - } - - return await HandleLoginOrRegistration(provider, oidcService, callbackData); - } - - return BadRequest("Unsupported flow type"); - } - - private async Task HandleManualConnection( - string provider, - OidcService oidcService, - OidcCallbackData callbackData, - Guid accountId - ) - { - provider = provider.ToLower(); - - OidcUserInfo userInfo; - try - { - userInfo = await oidcService.ProcessCallbackAsync(callbackData); - } - catch (Exception ex) - { - return BadRequest($"Error processing {provider} authentication: {ex.Message}"); - } - - if (string.IsNullOrEmpty(userInfo.UserId)) - { - return BadRequest($"{provider} did not return a valid user identifier."); - } - - // Extract device ID from the callback state if available - var deviceId = !string.IsNullOrEmpty(callbackData.State) ? callbackData.State : string.Empty; - - // Check if this provider account is already connected to any user - var existingConnection = await db.AccountConnections - .FirstOrDefaultAsync(c => - c.Provider == provider && - c.ProvidedIdentifier == userInfo.UserId); - - // If it's connected to a different user, return error - if (existingConnection != null && existingConnection.AccountId != accountId) - { - return BadRequest($"This {provider} account is already linked to another user."); - } - - // Check if the current user already has this provider connected - var userHasProvider = await db.AccountConnections - .AnyAsync(c => - c.AccountId == accountId && - c.Provider == provider); - - if (userHasProvider) - { - // Update existing connection with new tokens - var connection = await db.AccountConnections - .FirstOrDefaultAsync(c => - c.AccountId == accountId && - c.Provider == provider); - - if (connection != null) - { - connection.AccessToken = userInfo.AccessToken; - connection.RefreshToken = userInfo.RefreshToken; - connection.LastUsedAt = SystemClock.Instance.GetCurrentInstant(); - connection.Meta = userInfo.ToMetadata(); - } - } - else - { - // Create new connection - db.AccountConnections.Add(new AccountConnection - { - AccountId = accountId, - Provider = provider, - ProvidedIdentifier = userInfo.UserId!, - AccessToken = userInfo.AccessToken, - RefreshToken = userInfo.RefreshToken, - LastUsedAt = SystemClock.Instance.GetCurrentInstant(), - Meta = userInfo.ToMetadata(), - }); - } - - try - { - await db.SaveChangesAsync(); - } - catch (DbUpdateException) - { - return StatusCode(500, $"Failed to save {provider} connection. Please try again."); - } - - // Clean up and redirect - var returnUrlKey = $"{ReturnUrlCachePrefix}{callbackData.State}"; - var returnUrl = await cache.GetAsync(returnUrlKey); - await cache.RemoveAsync(returnUrlKey); - - return Redirect(string.IsNullOrEmpty(returnUrl) ? "/auth/callback" : returnUrl); - } - - private async Task HandleLoginOrRegistration( - string provider, - OidcService oidcService, - OidcCallbackData callbackData - ) - { - OidcUserInfo userInfo; - try - { - userInfo = await oidcService.ProcessCallbackAsync(callbackData); - } - catch (Exception ex) - { - return BadRequest($"Error processing callback: {ex.Message}"); - } - - if (string.IsNullOrEmpty(userInfo.Email) || string.IsNullOrEmpty(userInfo.UserId)) - { - return BadRequest($"Email or user ID is missing from {provider}'s response"); - } - - var connection = await db.AccountConnections - .Include(c => c.Account) - .FirstOrDefaultAsync(c => c.Provider == provider && c.ProvidedIdentifier == userInfo.UserId); - - var clock = SystemClock.Instance; - if (connection != null) - { - // Login existing user - var deviceId = !string.IsNullOrEmpty(callbackData.State) ? - callbackData.State.Split('|').FirstOrDefault() : - string.Empty; - - var challenge = await oidcService.CreateChallengeForUserAsync( - userInfo, - connection.Account, - HttpContext, - deviceId ?? string.Empty); - return Redirect($"/auth/callback?challenge={challenge.Id}"); - } - - // Register new user - var account = await accounts.LookupAccount(userInfo.Email) ?? await accounts.CreateAccount(userInfo); - - // Create connection for new or existing user - var newConnection = new AccountConnection - { - Account = account, - Provider = provider, - ProvidedIdentifier = userInfo.UserId!, - AccessToken = userInfo.AccessToken, - RefreshToken = userInfo.RefreshToken, - LastUsedAt = clock.GetCurrentInstant(), - Meta = userInfo.ToMetadata() - }; - db.AccountConnections.Add(newConnection); - - await db.SaveChangesAsync(); - - var loginSession = await auth.CreateSessionForOidcAsync(account, clock.GetCurrentInstant()); - var loginToken = auth.CreateToken(loginSession); - return Redirect($"/auth/token?token={loginToken}"); - } - - private static async Task ExtractCallbackData(HttpRequest request) - { - var data = new OidcCallbackData(); - switch (request.Method) - { - case "GET": - data.Code = Uri.UnescapeDataString(request.Query["code"].FirstOrDefault() ?? ""); - data.IdToken = Uri.UnescapeDataString(request.Query["id_token"].FirstOrDefault() ?? ""); - data.State = Uri.UnescapeDataString(request.Query["state"].FirstOrDefault() ?? ""); - break; - case "POST" when request.HasFormContentType: - { - var form = await request.ReadFormAsync(); - data.Code = Uri.UnescapeDataString(form["code"].FirstOrDefault() ?? ""); - data.IdToken = Uri.UnescapeDataString(form["id_token"].FirstOrDefault() ?? ""); - data.State = Uri.UnescapeDataString(form["state"].FirstOrDefault() ?? ""); - if (form.ContainsKey("user")) - data.RawData = Uri.UnescapeDataString(form["user"].FirstOrDefault() ?? ""); - - break; - } - } - - return data; - } -} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Auth/OpenId/DiscordOidcService.cs b/DysonNetwork.Sphere/Auth/OpenId/DiscordOidcService.cs deleted file mode 100644 index c710b71..0000000 --- a/DysonNetwork.Sphere/Auth/OpenId/DiscordOidcService.cs +++ /dev/null @@ -1,115 +0,0 @@ -using System.Net.Http.Json; -using System.Text.Json; -using DysonNetwork.Sphere.Storage; - -namespace DysonNetwork.Sphere.Auth.OpenId; - -public class DiscordOidcService( - IConfiguration configuration, - IHttpClientFactory httpClientFactory, - AppDatabase db, - AuthService auth, - ICacheService cache -) - : OidcService(configuration, httpClientFactory, db, auth, cache) -{ - public override string ProviderName => "Discord"; - protected override string DiscoveryEndpoint => ""; // Discord doesn't have a standard OIDC discovery endpoint - protected override string ConfigSectionName => "Discord"; - - public override string GetAuthorizationUrl(string state, string nonce) - { - var config = GetProviderConfig(); - var queryParams = new Dictionary - { - { "client_id", config.ClientId }, - { "redirect_uri", config.RedirectUri }, - { "response_type", "code" }, - { "scope", "identify email" }, - { "state", state }, - }; - - var queryString = string.Join("&", queryParams.Select(p => $"{p.Key}={Uri.EscapeDataString(p.Value)}")); - return $"https://discord.com/oauth2/authorize?{queryString}"; - } - - protected override Task GetDiscoveryDocumentAsync() - { - return Task.FromResult(new OidcDiscoveryDocument - { - AuthorizationEndpoint = "https://discord.com/oauth2/authorize", - TokenEndpoint = "https://discord.com/oauth2/token", - UserinfoEndpoint = "https://discord.com/users/@me", - JwksUri = null - })!; - } - - public override async Task ProcessCallbackAsync(OidcCallbackData callbackData) - { - var tokenResponse = await ExchangeCodeForTokensAsync(callbackData.Code); - if (tokenResponse?.AccessToken == null) - { - throw new InvalidOperationException("Failed to obtain access token from Discord"); - } - - var userInfo = await GetUserInfoAsync(tokenResponse.AccessToken); - - userInfo.AccessToken = tokenResponse.AccessToken; - userInfo.RefreshToken = tokenResponse.RefreshToken; - - return userInfo; - } - - protected override async Task ExchangeCodeForTokensAsync(string code, - string? codeVerifier = null) - { - var config = GetProviderConfig(); - var client = HttpClientFactory.CreateClient(); - - var content = new FormUrlEncodedContent(new Dictionary - { - { "client_id", config.ClientId }, - { "client_secret", config.ClientSecret }, - { "grant_type", "authorization_code" }, - { "code", code }, - { "redirect_uri", config.RedirectUri }, - }); - - var response = await client.PostAsync("https://discord.com/oauth2/token", content); - response.EnsureSuccessStatusCode(); - - return await response.Content.ReadFromJsonAsync(); - } - - private async Task GetUserInfoAsync(string accessToken) - { - var client = HttpClientFactory.CreateClient(); - var request = new HttpRequestMessage(HttpMethod.Get, "https://discord.com/users/@me"); - request.Headers.Add("Authorization", $"Bearer {accessToken}"); - - var response = await client.SendAsync(request); - response.EnsureSuccessStatusCode(); - - var json = await response.Content.ReadAsStringAsync(); - var discordUser = JsonDocument.Parse(json).RootElement; - - var userId = discordUser.GetProperty("id").GetString() ?? ""; - var avatar = discordUser.TryGetProperty("avatar", out var avatarElement) ? avatarElement.GetString() : null; - - return new OidcUserInfo - { - UserId = userId, - Email = (discordUser.TryGetProperty("email", out var emailElement) ? emailElement.GetString() : null) ?? "", - EmailVerified = discordUser.TryGetProperty("verified", out var verifiedElement) && - verifiedElement.GetBoolean(), - DisplayName = (discordUser.TryGetProperty("global_name", out var globalNameElement) - ? globalNameElement.GetString() - : null) ?? "", - PreferredUsername = discordUser.GetProperty("username").GetString() ?? "", - ProfilePictureUrl = !string.IsNullOrEmpty(avatar) - ? $"https://cdn.discordapp.com/avatars/{userId}/{avatar}.png" - : "", - Provider = ProviderName - }; - } -} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Auth/OpenId/GitHubOidcService.cs b/DysonNetwork.Sphere/Auth/OpenId/GitHubOidcService.cs deleted file mode 100644 index fc80bfe..0000000 --- a/DysonNetwork.Sphere/Auth/OpenId/GitHubOidcService.cs +++ /dev/null @@ -1,127 +0,0 @@ -using System.Net.Http.Json; -using System.Text.Json; -using DysonNetwork.Sphere.Storage; - -namespace DysonNetwork.Sphere.Auth.OpenId; - -public class GitHubOidcService( - IConfiguration configuration, - IHttpClientFactory httpClientFactory, - AppDatabase db, - AuthService auth, - ICacheService cache -) - : OidcService(configuration, httpClientFactory, db, auth, cache) -{ - public override string ProviderName => "GitHub"; - protected override string DiscoveryEndpoint => ""; // GitHub doesn't have a standard OIDC discovery endpoint - protected override string ConfigSectionName => "GitHub"; - - public override string GetAuthorizationUrl(string state, string nonce) - { - var config = GetProviderConfig(); - var queryParams = new Dictionary - { - { "client_id", config.ClientId }, - { "redirect_uri", config.RedirectUri }, - { "scope", "user:email" }, - { "state", state }, - }; - - var queryString = string.Join("&", queryParams.Select(p => $"{p.Key}={Uri.EscapeDataString(p.Value)}")); - return $"https://github.com/login/oauth/authorize?{queryString}"; - } - - public override async Task ProcessCallbackAsync(OidcCallbackData callbackData) - { - var tokenResponse = await ExchangeCodeForTokensAsync(callbackData.Code); - if (tokenResponse?.AccessToken == null) - { - throw new InvalidOperationException("Failed to obtain access token from GitHub"); - } - - var userInfo = await GetUserInfoAsync(tokenResponse.AccessToken); - - userInfo.AccessToken = tokenResponse.AccessToken; - userInfo.RefreshToken = tokenResponse.RefreshToken; - - return userInfo; - } - - protected override async Task ExchangeCodeForTokensAsync(string code, - string? codeVerifier = null) - { - var config = GetProviderConfig(); - var client = HttpClientFactory.CreateClient(); - - var tokenRequest = new HttpRequestMessage(HttpMethod.Post, "https://github.com/login/oauth/access_token") - { - Content = new FormUrlEncodedContent(new Dictionary - { - { "client_id", config.ClientId }, - { "client_secret", config.ClientSecret }, - { "code", code }, - { "redirect_uri", config.RedirectUri }, - }) - }; - tokenRequest.Headers.Add("Accept", "application/json"); - - var response = await client.SendAsync(tokenRequest); - response.EnsureSuccessStatusCode(); - - return await response.Content.ReadFromJsonAsync(); - } - - private async Task GetUserInfoAsync(string accessToken) - { - var client = HttpClientFactory.CreateClient(); - var request = new HttpRequestMessage(HttpMethod.Get, "https://api.github.com/user"); - request.Headers.Add("Authorization", $"Bearer {accessToken}"); - request.Headers.Add("User-Agent", "DysonNetwork.Sphere"); - - var response = await client.SendAsync(request); - response.EnsureSuccessStatusCode(); - - var json = await response.Content.ReadAsStringAsync(); - var githubUser = JsonDocument.Parse(json).RootElement; - - var email = githubUser.TryGetProperty("email", out var emailElement) ? emailElement.GetString() : null; - if (string.IsNullOrEmpty(email)) - { - email = await GetPrimaryEmailAsync(accessToken); - } - - return new OidcUserInfo - { - UserId = githubUser.GetProperty("id").GetInt64().ToString(), - Email = email, - DisplayName = githubUser.TryGetProperty("name", out var nameElement) ? nameElement.GetString() ?? "" : "", - PreferredUsername = githubUser.GetProperty("login").GetString() ?? "", - ProfilePictureUrl = githubUser.TryGetProperty("avatar_url", out var avatarElement) - ? avatarElement.GetString() ?? "" - : "", - Provider = ProviderName - }; - } - - private async Task GetPrimaryEmailAsync(string accessToken) - { - var client = HttpClientFactory.CreateClient(); - var request = new HttpRequestMessage(HttpMethod.Get, "https://api.github.com/user/emails"); - request.Headers.Add("Authorization", $"Bearer {accessToken}"); - request.Headers.Add("User-Agent", "DysonNetwork.Sphere"); - - var response = await client.SendAsync(request); - if (!response.IsSuccessStatusCode) return null; - - var emails = await response.Content.ReadFromJsonAsync>(); - return emails?.FirstOrDefault(e => e.Primary)?.Email; - } - - private class GitHubEmail - { - public string Email { get; set; } = ""; - public bool Primary { get; set; } - public bool Verified { get; set; } - } -} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Auth/OpenId/GoogleOidcService.cs b/DysonNetwork.Sphere/Auth/OpenId/GoogleOidcService.cs deleted file mode 100644 index a446b2e..0000000 --- a/DysonNetwork.Sphere/Auth/OpenId/GoogleOidcService.cs +++ /dev/null @@ -1,136 +0,0 @@ -using System.IdentityModel.Tokens.Jwt; -using System.Net.Http.Json; -using System.Security.Cryptography; -using System.Text; -using DysonNetwork.Sphere.Storage; -using Microsoft.IdentityModel.Tokens; - -namespace DysonNetwork.Sphere.Auth.OpenId; - -public class GoogleOidcService( - IConfiguration configuration, - IHttpClientFactory httpClientFactory, - AppDatabase db, - AuthService auth, - ICacheService cache -) - : OidcService(configuration, httpClientFactory, db, auth, cache) -{ - private readonly IHttpClientFactory _httpClientFactory = httpClientFactory; - - public override string ProviderName => "google"; - protected override string DiscoveryEndpoint => "https://accounts.google.com/.well-known/openid-configuration"; - protected override string ConfigSectionName => "Google"; - - public override string GetAuthorizationUrl(string state, string nonce) - { - var config = GetProviderConfig(); - var discoveryDocument = GetDiscoveryDocumentAsync().GetAwaiter().GetResult(); - - if (discoveryDocument?.AuthorizationEndpoint == null) - { - throw new InvalidOperationException("Authorization endpoint not found in discovery document"); - } - - var queryParams = new Dictionary - { - { "client_id", config.ClientId }, - { "redirect_uri", config.RedirectUri }, - { "response_type", "code" }, - { "scope", "openid email profile" }, - { "state", state }, // No '|codeVerifier' appended anymore - { "nonce", nonce } - }; - - var queryString = string.Join("&", queryParams.Select(p => $"{p.Key}={Uri.EscapeDataString(p.Value)}")); - return $"{discoveryDocument.AuthorizationEndpoint}?{queryString}"; - } - - public override async Task ProcessCallbackAsync(OidcCallbackData callbackData) - { - // No need to split or parse code verifier from state - var state = callbackData.State ?? ""; - callbackData.State = state; // Keep the original state if needed - - // Exchange the code for tokens - // Pass null or omit the parameter for codeVerifier as PKCE is removed - var tokenResponse = await ExchangeCodeForTokensAsync(callbackData.Code, null); - if (tokenResponse?.IdToken == null) - { - throw new InvalidOperationException("Failed to obtain ID token from Google"); - } - - // Validate the ID token - var userInfo = await ValidateTokenAsync(tokenResponse.IdToken); - - // Set tokens on the user info - userInfo.AccessToken = tokenResponse.AccessToken; - userInfo.RefreshToken = tokenResponse.RefreshToken; - - // Try to fetch additional profile data if userinfo endpoint is available - try - { - var discoveryDocument = await GetDiscoveryDocumentAsync(); - if (discoveryDocument?.UserinfoEndpoint != null && !string.IsNullOrEmpty(tokenResponse.AccessToken)) - { - var client = _httpClientFactory.CreateClient(); - client.DefaultRequestHeaders.Authorization = - new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", tokenResponse.AccessToken); - - var userInfoResponse = - await client.GetFromJsonAsync>(discoveryDocument.UserinfoEndpoint); - - if (userInfoResponse != null) - { - if (userInfoResponse.TryGetValue("picture", out var picture) && picture != null) - { - userInfo.ProfilePictureUrl = picture.ToString(); - } - } - } - } - catch - { - // Ignore errors when fetching additional profile data - } - - return userInfo; - } - - private async Task ValidateTokenAsync(string idToken) - { - var discoveryDocument = await GetDiscoveryDocumentAsync(); - if (discoveryDocument?.JwksUri == null) - { - throw new InvalidOperationException("JWKS URI not found in discovery document"); - } - - var client = _httpClientFactory.CreateClient(); - var jwksResponse = await client.GetFromJsonAsync(discoveryDocument.JwksUri); - if (jwksResponse == null) - { - throw new InvalidOperationException("Failed to retrieve JWKS from Google"); - } - - var handler = new JwtSecurityTokenHandler(); - var jwtToken = handler.ReadJwtToken(idToken); - var kid = jwtToken.Header.Kid; - var signingKey = jwksResponse.Keys.FirstOrDefault(k => k.Kid == kid); - if (signingKey == null) - { - throw new SecurityTokenValidationException("Unable to find matching key in Google's JWKS"); - } - - var validationParameters = new TokenValidationParameters - { - ValidateIssuer = true, - ValidIssuer = "https://accounts.google.com", - ValidateAudience = true, - ValidAudience = GetProviderConfig().ClientId, - ValidateLifetime = true, - IssuerSigningKey = signingKey - }; - - return ValidateAndExtractIdToken(idToken, validationParameters); - } -} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Auth/OpenId/MicrosoftOidcService.cs b/DysonNetwork.Sphere/Auth/OpenId/MicrosoftOidcService.cs deleted file mode 100644 index 83efad1..0000000 --- a/DysonNetwork.Sphere/Auth/OpenId/MicrosoftOidcService.cs +++ /dev/null @@ -1,124 +0,0 @@ -using System.Net.Http.Json; -using System.Text.Json; -using DysonNetwork.Sphere.Storage; - -namespace DysonNetwork.Sphere.Auth.OpenId; - -public class MicrosoftOidcService( - IConfiguration configuration, - IHttpClientFactory httpClientFactory, - AppDatabase db, - AuthService auth, - ICacheService cache -) - : OidcService(configuration, httpClientFactory, db, auth, cache) -{ - public override string ProviderName => "Microsoft"; - - protected override string DiscoveryEndpoint => Configuration[$"Oidc:{ConfigSectionName}:DiscoveryEndpoint"] ?? - throw new InvalidOperationException( - "Microsoft OIDC discovery endpoint is not configured."); - - protected override string ConfigSectionName => "Microsoft"; - - public override string GetAuthorizationUrl(string state, string nonce) - { - var config = GetProviderConfig(); - var discoveryDocument = GetDiscoveryDocumentAsync().GetAwaiter().GetResult(); - if (discoveryDocument?.AuthorizationEndpoint == null) - throw new InvalidOperationException("Authorization endpoint not found in discovery document."); - - var queryParams = new Dictionary - { - { "client_id", config.ClientId }, - { "response_type", "code" }, - { "redirect_uri", config.RedirectUri }, - { "response_mode", "query" }, - { "scope", "openid profile email" }, - { "state", state }, - { "nonce", nonce }, - }; - - var queryString = string.Join("&", queryParams.Select(p => $"{p.Key}={Uri.EscapeDataString(p.Value)}")); - return $"{discoveryDocument.AuthorizationEndpoint}?{queryString}"; - } - - public override async Task ProcessCallbackAsync(OidcCallbackData callbackData) - { - var tokenResponse = await ExchangeCodeForTokensAsync(callbackData.Code); - if (tokenResponse?.AccessToken == null) - { - throw new InvalidOperationException("Failed to obtain access token from Microsoft"); - } - - var userInfo = await GetUserInfoAsync(tokenResponse.AccessToken); - - userInfo.AccessToken = tokenResponse.AccessToken; - userInfo.RefreshToken = tokenResponse.RefreshToken; - - return userInfo; - } - - protected override async Task ExchangeCodeForTokensAsync(string code, - string? codeVerifier = null) - { - var config = GetProviderConfig(); - var discoveryDocument = await GetDiscoveryDocumentAsync(); - if (discoveryDocument?.TokenEndpoint == null) - { - throw new InvalidOperationException("Token endpoint not found in discovery document."); - } - - var client = HttpClientFactory.CreateClient(); - - var tokenRequest = new HttpRequestMessage(HttpMethod.Post, discoveryDocument.TokenEndpoint) - { - Content = new FormUrlEncodedContent(new Dictionary - { - { "client_id", config.ClientId }, - { "scope", "openid profile email" }, - { "code", code }, - { "redirect_uri", config.RedirectUri }, - { "grant_type", "authorization_code" }, - { "client_secret", config.ClientSecret }, - }) - }; - - var response = await client.SendAsync(tokenRequest); - response.EnsureSuccessStatusCode(); - - return await response.Content.ReadFromJsonAsync(); - } - - private async Task GetUserInfoAsync(string accessToken) - { - var discoveryDocument = await GetDiscoveryDocumentAsync(); - if (discoveryDocument?.UserinfoEndpoint == null) - throw new InvalidOperationException("Userinfo endpoint not found in discovery document."); - - var client = HttpClientFactory.CreateClient(); - var request = new HttpRequestMessage(HttpMethod.Get, discoveryDocument.UserinfoEndpoint); - request.Headers.Add("Authorization", $"Bearer {accessToken}"); - - var response = await client.SendAsync(request); - response.EnsureSuccessStatusCode(); - - var json = await response.Content.ReadAsStringAsync(); - var microsoftUser = JsonDocument.Parse(json).RootElement; - - return new OidcUserInfo - { - UserId = microsoftUser.GetProperty("sub").GetString() ?? "", - Email = microsoftUser.TryGetProperty("email", out var emailElement) ? emailElement.GetString() : null, - DisplayName = - microsoftUser.TryGetProperty("name", out var nameElement) ? nameElement.GetString() ?? "" : "", - PreferredUsername = microsoftUser.TryGetProperty("preferred_username", out var preferredUsernameElement) - ? preferredUsernameElement.GetString() ?? "" - : "", - ProfilePictureUrl = microsoftUser.TryGetProperty("picture", out var pictureElement) - ? pictureElement.GetString() ?? "" - : "", - Provider = ProviderName - }; - } -} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Auth/OpenId/OidcController.cs b/DysonNetwork.Sphere/Auth/OpenId/OidcController.cs deleted file mode 100644 index 4324011..0000000 --- a/DysonNetwork.Sphere/Auth/OpenId/OidcController.cs +++ /dev/null @@ -1,194 +0,0 @@ -using DysonNetwork.Sphere.Account; -using DysonNetwork.Sphere.Storage; -using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; -using Microsoft.IdentityModel.Tokens; -using NodaTime; - -namespace DysonNetwork.Sphere.Auth.OpenId; - -[ApiController] -[Route("/api/auth/login")] -public class OidcController( - IServiceProvider serviceProvider, - AppDatabase db, - AccountService accounts, - ICacheService cache -) - : ControllerBase -{ - private const string StateCachePrefix = "oidc-state:"; - private static readonly TimeSpan StateExpiration = TimeSpan.FromMinutes(15); - - [HttpGet("{provider}")] - public async Task OidcLogin( - [FromRoute] string provider, - [FromQuery] string? returnUrl = "/", - [FromHeader(Name = "X-Device-Id")] string? deviceId = null - ) - { - try - { - var oidcService = GetOidcService(provider); - - // If the user is already authenticated, treat as an account connection request - if (HttpContext.Items["CurrentUser"] is Account.Account currentUser) - { - var state = Guid.NewGuid().ToString(); - var nonce = Guid.NewGuid().ToString(); - - // Create and store connection state - var oidcState = OidcState.ForConnection(currentUser.Id, provider, nonce, deviceId); - await cache.SetAsync($"{StateCachePrefix}{state}", oidcState, StateExpiration); - - // The state parameter sent to the provider is the GUID key for the cache. - var authUrl = oidcService.GetAuthorizationUrl(state, nonce); - return Redirect(authUrl); - } - else // Otherwise, proceed with the login / registration flow - { - var nonce = Guid.NewGuid().ToString(); - var state = Guid.NewGuid().ToString(); - - // Create login state with return URL and device ID - var oidcState = OidcState.ForLogin(returnUrl ?? "/", deviceId); - await cache.SetAsync($"{StateCachePrefix}{state}", oidcState, StateExpiration); - var authUrl = oidcService.GetAuthorizationUrl(state, nonce); - return Redirect(authUrl); - } - } - catch (Exception ex) - { - return BadRequest($"Error initiating OpenID Connect flow: {ex.Message}"); - } - } - - /// - /// Mobile Apple Sign In endpoint - /// Handles Apple authentication directly from mobile apps - /// - [HttpPost("apple/mobile")] - public async Task> AppleMobileLogin( - [FromBody] AppleMobileSignInRequest request) - { - try - { - // Get Apple OIDC service - if (GetOidcService("apple") is not AppleOidcService appleService) - return StatusCode(503, "Apple OIDC service not available"); - - // Prepare callback data for processing - var callbackData = new OidcCallbackData - { - IdToken = request.IdentityToken, - Code = request.AuthorizationCode, - }; - - // Process the authentication - var userInfo = await appleService.ProcessCallbackAsync(callbackData); - - // Find or create user account using existing logic - var account = await FindOrCreateAccount(userInfo, "apple"); - - // Create session using the OIDC service - var challenge = await appleService.CreateChallengeForUserAsync( - userInfo, - account, - HttpContext, - request.DeviceId - ); - - return Ok(challenge); - } - catch (SecurityTokenValidationException ex) - { - return Unauthorized($"Invalid identity token: {ex.Message}"); - } - catch (Exception ex) - { - // Log the error - return StatusCode(500, $"Authentication failed: {ex.Message}"); - } - } - - private OidcService GetOidcService(string provider) - { - return provider.ToLower() switch - { - "apple" => serviceProvider.GetRequiredService(), - "google" => serviceProvider.GetRequiredService(), - "microsoft" => serviceProvider.GetRequiredService(), - "discord" => serviceProvider.GetRequiredService(), - "github" => serviceProvider.GetRequiredService(), - "afdian" => serviceProvider.GetRequiredService(), - _ => throw new ArgumentException($"Unsupported provider: {provider}") - }; - } - - private async Task FindOrCreateAccount(OidcUserInfo userInfo, string provider) - { - if (string.IsNullOrEmpty(userInfo.Email)) - throw new ArgumentException("Email is required for account creation"); - - // Check if an account exists by email - var existingAccount = await accounts.LookupAccount(userInfo.Email); - if (existingAccount != null) - { - // Check if this provider connection already exists - var existingConnection = await db.AccountConnections - .FirstOrDefaultAsync(c => c.AccountId == existingAccount.Id && - c.Provider == provider && - c.ProvidedIdentifier == userInfo.UserId); - - // If no connection exists, create one - if (existingConnection != null) - { - await db.AccountConnections - .Where(c => c.AccountId == existingAccount.Id && - c.Provider == provider && - c.ProvidedIdentifier == userInfo.UserId) - .ExecuteUpdateAsync(s => s - .SetProperty(c => c.LastUsedAt, SystemClock.Instance.GetCurrentInstant()) - .SetProperty(c => c.Meta, userInfo.ToMetadata())); - - return existingAccount; - } - - var connection = new AccountConnection - { - AccountId = existingAccount.Id, - Provider = provider, - ProvidedIdentifier = userInfo.UserId!, - AccessToken = userInfo.AccessToken, - RefreshToken = userInfo.RefreshToken, - LastUsedAt = SystemClock.Instance.GetCurrentInstant(), - Meta = userInfo.ToMetadata() - }; - - await db.AccountConnections.AddAsync(connection); - await db.SaveChangesAsync(); - - return existingAccount; - } - - // Create new account using the AccountService - var newAccount = await accounts.CreateAccount(userInfo); - - // Create the provider connection - var newConnection = new AccountConnection - { - AccountId = newAccount.Id, - Provider = provider, - ProvidedIdentifier = userInfo.UserId!, - AccessToken = userInfo.AccessToken, - RefreshToken = userInfo.RefreshToken, - LastUsedAt = SystemClock.Instance.GetCurrentInstant(), - Meta = userInfo.ToMetadata() - }; - - db.AccountConnections.Add(newConnection); - await db.SaveChangesAsync(); - - return newAccount; - } -} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Auth/OpenId/OidcService.cs b/DysonNetwork.Sphere/Auth/OpenId/OidcService.cs deleted file mode 100644 index 544704a..0000000 --- a/DysonNetwork.Sphere/Auth/OpenId/OidcService.cs +++ /dev/null @@ -1,295 +0,0 @@ -using System.IdentityModel.Tokens.Jwt; -using System.Net.Http.Json; -using System.Text.Json.Serialization; -using DysonNetwork.Sphere.Account; -using DysonNetwork.Sphere.Storage; -using Microsoft.EntityFrameworkCore; -using Microsoft.IdentityModel.Tokens; -using NodaTime; - -namespace DysonNetwork.Sphere.Auth.OpenId; - -/// -/// Base service for OpenID Connect authentication providers -/// -public abstract class OidcService( - IConfiguration configuration, - IHttpClientFactory httpClientFactory, - AppDatabase db, - AuthService auth, - ICacheService cache -) -{ - protected readonly IConfiguration Configuration = configuration; - protected readonly IHttpClientFactory HttpClientFactory = httpClientFactory; - protected readonly AppDatabase Db = db; - - /// - /// Gets the unique identifier for this provider - /// - public abstract string ProviderName { get; } - - /// - /// Gets the OIDC discovery document endpoint - /// - protected abstract string DiscoveryEndpoint { get; } - - /// - /// Gets configuration section name for this provider - /// - protected abstract string ConfigSectionName { get; } - - /// - /// Gets the authorization URL for initiating the authentication flow - /// - public abstract string GetAuthorizationUrl(string state, string nonce); - - /// - /// Process the callback from the OIDC provider - /// - public abstract Task ProcessCallbackAsync(OidcCallbackData callbackData); - - /// - /// Gets the provider configuration - /// - protected ProviderConfiguration GetProviderConfig() - { - return new ProviderConfiguration - { - ClientId = Configuration[$"Oidc:{ConfigSectionName}:ClientId"] ?? "", - ClientSecret = Configuration[$"Oidc:{ConfigSectionName}:ClientSecret"] ?? "", - RedirectUri = Configuration["BaseUrl"] + "/auth/callback/" + ProviderName.ToLower() - }; - } - - /// - /// Retrieves the OpenID Connect discovery document - /// - protected virtual async Task GetDiscoveryDocumentAsync() - { - // Construct a cache key unique to the current provider: - var cacheKey = $"oidc-discovery:{ProviderName}"; - - // Try getting the discovery document from cache first: - var (found, cachedDoc) = await cache.GetAsyncWithStatus(cacheKey); - if (found && cachedDoc != null) - { - return cachedDoc; - } - - // If it's not cached, fetch from the actual discovery endpoint: - var client = HttpClientFactory.CreateClient(); - var response = await client.GetAsync(DiscoveryEndpoint); - response.EnsureSuccessStatusCode(); - var doc = await response.Content.ReadFromJsonAsync(); - - // Store the discovery document in the cache for a while (e.g., 15 minutes): - if (doc is not null) - await cache.SetAsync(cacheKey, doc, TimeSpan.FromMinutes(15)); - - return doc; - - } - - /// - /// Exchange the authorization code for tokens - /// - protected virtual async Task ExchangeCodeForTokensAsync(string code, - string? codeVerifier = null) - { - var config = GetProviderConfig(); - var discoveryDocument = await GetDiscoveryDocumentAsync(); - - if (discoveryDocument?.TokenEndpoint == null) - { - throw new InvalidOperationException("Token endpoint not found in discovery document"); - } - - var client = HttpClientFactory.CreateClient(); - var content = new FormUrlEncodedContent(BuildTokenRequestParameters(code, config, codeVerifier)); - - var response = await client.PostAsync(discoveryDocument.TokenEndpoint, content); - response.EnsureSuccessStatusCode(); - - return await response.Content.ReadFromJsonAsync(); - } - - /// - /// Build the token request parameters - /// - protected virtual Dictionary BuildTokenRequestParameters(string code, ProviderConfiguration config, - string? codeVerifier) - { - var parameters = new Dictionary - { - { "client_id", config.ClientId }, - { "code", code }, - { "grant_type", "authorization_code" }, - { "redirect_uri", config.RedirectUri } - }; - - if (!string.IsNullOrEmpty(config.ClientSecret)) - { - parameters.Add("client_secret", config.ClientSecret); - } - - if (!string.IsNullOrEmpty(codeVerifier)) - { - parameters.Add("code_verifier", codeVerifier); - } - - return parameters; - } - - /// - /// Validates and extracts information from an ID token - /// - protected virtual OidcUserInfo ValidateAndExtractIdToken(string idToken, - TokenValidationParameters validationParameters) - { - var handler = new JwtSecurityTokenHandler(); - handler.ValidateToken(idToken, validationParameters, out _); - - var jwtToken = handler.ReadJwtToken(idToken); - - // Extract standard claims - var userId = jwtToken.Claims.FirstOrDefault(c => c.Type == "sub")?.Value; - var email = jwtToken.Claims.FirstOrDefault(c => c.Type == "email")?.Value; - var emailVerified = jwtToken.Claims.FirstOrDefault(c => c.Type == "email_verified")?.Value == "true"; - var name = jwtToken.Claims.FirstOrDefault(c => c.Type == "name")?.Value; - var givenName = jwtToken.Claims.FirstOrDefault(c => c.Type == "given_name")?.Value; - var familyName = jwtToken.Claims.FirstOrDefault(c => c.Type == "family_name")?.Value; - var preferredUsername = jwtToken.Claims.FirstOrDefault(c => c.Type == "preferred_username")?.Value; - var picture = jwtToken.Claims.FirstOrDefault(c => c.Type == "picture")?.Value; - - // Determine preferred username - try different options - var username = preferredUsername; - if (string.IsNullOrEmpty(username)) - { - // Fall back to email local part if no preferred username - username = !string.IsNullOrEmpty(email) ? email.Split('@')[0] : null; - } - - return new OidcUserInfo - { - UserId = userId, - Email = email, - EmailVerified = emailVerified, - FirstName = givenName ?? "", - LastName = familyName ?? "", - DisplayName = name ?? $"{givenName} {familyName}".Trim(), - PreferredUsername = username ?? "", - ProfilePictureUrl = picture, - Provider = ProviderName - }; - } - - /// - /// Creates a challenge and session for an authenticated user - /// Also creates or updates the account connection - /// - public async Task CreateChallengeForUserAsync( - OidcUserInfo userInfo, - Account.Account account, - HttpContext request, - string deviceId - ) - { - // Create or update the account connection - var connection = await Db.AccountConnections - .FirstOrDefaultAsync(c => c.Provider == ProviderName && - c.ProvidedIdentifier == userInfo.UserId && - c.AccountId == account.Id - ); - - if (connection is null) - { - connection = new AccountConnection - { - Provider = ProviderName, - ProvidedIdentifier = userInfo.UserId ?? "", - AccessToken = userInfo.AccessToken, - RefreshToken = userInfo.RefreshToken, - LastUsedAt = SystemClock.Instance.GetCurrentInstant(), - AccountId = account.Id - }; - await Db.AccountConnections.AddAsync(connection); - } - - // Create a challenge that's already completed - var now = SystemClock.Instance.GetCurrentInstant(); - var challenge = new Challenge - { - ExpiredAt = now.Plus(Duration.FromHours(1)), - StepTotal = await auth.DetectChallengeRisk(request.Request, account), - Type = ChallengeType.Oidc, - Platform = ChallengePlatform.Unidentified, - Audiences = [ProviderName], - Scopes = ["*"], - AccountId = account.Id, - DeviceId = deviceId, - IpAddress = request.Connection.RemoteIpAddress?.ToString() ?? null, - UserAgent = request.Request.Headers.UserAgent, - }; - challenge.StepRemain--; - if (challenge.StepRemain < 0) challenge.StepRemain = 0; - - await Db.AuthChallenges.AddAsync(challenge); - await Db.SaveChangesAsync(); - - return challenge; - } -} - -/// -/// Provider configuration from app settings -/// -public class ProviderConfiguration -{ - public string ClientId { get; set; } = ""; - public string ClientSecret { get; set; } = ""; - public string RedirectUri { get; set; } = ""; -} - -/// -/// OIDC Discovery Document -/// -public class OidcDiscoveryDocument -{ - [JsonPropertyName("authorization_endpoint")] - public string? AuthorizationEndpoint { get; set; } - - [JsonPropertyName("token_endpoint")] public string? TokenEndpoint { get; set; } - - [JsonPropertyName("userinfo_endpoint")] - public string? UserinfoEndpoint { get; set; } - - [JsonPropertyName("jwks_uri")] public string? JwksUri { get; set; } -} - -/// -/// Response from the token endpoint -/// -public class OidcTokenResponse -{ - [JsonPropertyName("access_token")] public string? AccessToken { get; set; } - - [JsonPropertyName("token_type")] public string? TokenType { get; set; } - - [JsonPropertyName("expires_in")] public int ExpiresIn { get; set; } - - [JsonPropertyName("refresh_token")] public string? RefreshToken { get; set; } - - [JsonPropertyName("id_token")] public string? IdToken { get; set; } -} - -/// -/// Data received in the callback from an OIDC provider -/// -public class OidcCallbackData -{ - public string Code { get; set; } = ""; - public string IdToken { get; set; } = ""; - public string? State { get; set; } - public string? RawData { get; set; } -} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Auth/OpenId/OidcState.cs b/DysonNetwork.Sphere/Auth/OpenId/OidcState.cs deleted file mode 100644 index 608956e..0000000 --- a/DysonNetwork.Sphere/Auth/OpenId/OidcState.cs +++ /dev/null @@ -1,189 +0,0 @@ -using System.Text.Json; -using System.Text.Json.Serialization; - -namespace DysonNetwork.Sphere.Auth.OpenId; - -/// -/// Represents the state parameter used in OpenID Connect flows. -/// Handles serialization and deserialization of the state parameter. -/// -public class OidcState -{ - /// - /// The type of OIDC flow (login or connect). - /// - public OidcFlowType FlowType { get; set; } - - /// - /// The account ID (for connect flow). - /// - public Guid? AccountId { get; set; } - - - /// - /// The OIDC provider name. - /// - public string? Provider { get; set; } - - - /// - /// The nonce for CSRF protection. - /// - public string? Nonce { get; set; } - - - /// - /// The device ID for the authentication request. - /// - public string? DeviceId { get; set; } - - - /// - /// The return URL after authentication (for login flow). - /// - public string? ReturnUrl { get; set; } - - - /// - /// Creates a new OidcState for a connection flow. - /// - public static OidcState ForConnection(Guid accountId, string provider, string nonce, string? deviceId = null) - { - return new OidcState - { - FlowType = OidcFlowType.Connect, - AccountId = accountId, - Provider = provider, - Nonce = nonce, - DeviceId = deviceId - }; - } - - /// - /// Creates a new OidcState for a login flow. - /// - public static OidcState ForLogin(string returnUrl = "/", string? deviceId = null) - { - return new OidcState - { - FlowType = OidcFlowType.Login, - ReturnUrl = returnUrl, - DeviceId = deviceId - }; - } - - /// - /// The version of the state format. - /// - public int Version { get; set; } = 1; - - /// - /// Serializes the state to a JSON string for use in OIDC flows. - /// - public string Serialize() - { - return JsonSerializer.Serialize(this, new JsonSerializerOptions - { - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull - }); - } - - /// - /// Attempts to parse a state string into an OidcState object. - /// - public static bool TryParse(string? stateString, out OidcState? state) - { - state = null; - - if (string.IsNullOrEmpty(stateString)) - return false; - - try - { - // First try to parse as JSON - try - { - state = JsonSerializer.Deserialize(stateString); - return state != null; - } - catch (JsonException) - { - // Not a JSON string, try legacy format for backward compatibility - return TryParseLegacyFormat(stateString, out state); - } - } - catch - { - return false; - } - } - - private static bool TryParseLegacyFormat(string stateString, out OidcState? state) - { - state = null; - var parts = stateString.Split('|'); - - // Check for connection flow format: {accountId}|{provider}|{nonce}|{deviceId}|connect - if (parts.Length >= 5 && - Guid.TryParse(parts[0], out var accountId) && - string.Equals(parts[^1], "connect", StringComparison.OrdinalIgnoreCase)) - { - state = new OidcState - { - FlowType = OidcFlowType.Connect, - AccountId = accountId, - Provider = parts[1], - Nonce = parts[2], - DeviceId = parts.Length >= 4 && !string.IsNullOrEmpty(parts[3]) ? parts[3] : null - }; - return true; - } - - // Check for login flow format: {returnUrl}|{deviceId}|login - if (parts.Length >= 2 && - parts.Length <= 3 && - (parts.Length < 3 || string.Equals(parts[^1], "login", StringComparison.OrdinalIgnoreCase))) - { - state = new OidcState - { - FlowType = OidcFlowType.Login, - ReturnUrl = parts[0], - DeviceId = parts.Length >= 2 && !string.IsNullOrEmpty(parts[1]) ? parts[1] : null - }; - return true; - } - - // Legacy format support (for backward compatibility) - if (parts.Length == 1) - { - state = new OidcState - { - FlowType = OidcFlowType.Login, - ReturnUrl = parts[0], - DeviceId = null - }; - return true; - } - - - return false; - } -} - -/// -/// Represents the type of OIDC flow. -/// -public enum OidcFlowType -{ - /// - /// Login or registration flow. - /// - Login, - - - /// - /// Account connection flow. - /// - Connect -} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Auth/OpenId/OidcUserInfo.cs b/DysonNetwork.Sphere/Auth/OpenId/OidcUserInfo.cs deleted file mode 100644 index fda81a1..0000000 --- a/DysonNetwork.Sphere/Auth/OpenId/OidcUserInfo.cs +++ /dev/null @@ -1,49 +0,0 @@ -namespace DysonNetwork.Sphere.Auth.OpenId; - -/// -/// Represents the user information from an OIDC provider -/// -public class OidcUserInfo -{ - public string? UserId { get; set; } - public string? Email { get; set; } - public bool EmailVerified { get; set; } - public string FirstName { get; set; } = ""; - public string LastName { get; set; } = ""; - public string DisplayName { get; set; } = ""; - public string PreferredUsername { get; set; } = ""; - public string? ProfilePictureUrl { get; set; } - public string Provider { get; set; } = ""; - public string? RefreshToken { get; set; } - public string? AccessToken { get; set; } - - public Dictionary ToMetadata() - { - var metadata = new Dictionary(); - - if (!string.IsNullOrWhiteSpace(UserId)) - metadata["user_id"] = UserId; - - if (!string.IsNullOrWhiteSpace(Email)) - metadata["email"] = Email; - - metadata["email_verified"] = EmailVerified; - - if (!string.IsNullOrWhiteSpace(FirstName)) - metadata["first_name"] = FirstName; - - if (!string.IsNullOrWhiteSpace(LastName)) - metadata["last_name"] = LastName; - - if (!string.IsNullOrWhiteSpace(DisplayName)) - metadata["display_name"] = DisplayName; - - if (!string.IsNullOrWhiteSpace(PreferredUsername)) - metadata["preferred_username"] = PreferredUsername; - - if (!string.IsNullOrWhiteSpace(ProfilePictureUrl)) - metadata["profile_picture_url"] = ProfilePictureUrl; - - return metadata; - } -} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Auth/Session.cs b/DysonNetwork.Sphere/Auth/Session.cs deleted file mode 100644 index ac00ab8..0000000 --- a/DysonNetwork.Sphere/Auth/Session.cs +++ /dev/null @@ -1,69 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; -using System.Text.Json.Serialization; -using DysonNetwork.Sphere.Developer; -using NodaTime; -using Point = NetTopologySuite.Geometries.Point; - -namespace DysonNetwork.Sphere.Auth; - -public class Session : ModelBase -{ - public Guid Id { get; set; } = Guid.NewGuid(); - [MaxLength(1024)] public string? Label { get; set; } - public Instant? LastGrantedAt { get; set; } - public Instant? ExpiredAt { get; set; } - - public Guid AccountId { get; set; } - [JsonIgnore] public Account.Account Account { get; set; } = null!; - public Guid ChallengeId { get; set; } - public Challenge Challenge { get; set; } = null!; - public Guid? AppId { get; set; } - public CustomApp? App { get; set; } -} - -public enum ChallengeType -{ - Login, - OAuth, // Trying to authorize other platforms - Oidc // Trying to connect other platforms -} - -public enum ChallengePlatform -{ - Unidentified, - Web, - Ios, - Android, - MacOs, - Windows, - Linux -} - -public class Challenge : ModelBase -{ - public Guid Id { get; set; } = Guid.NewGuid(); - public Instant? ExpiredAt { get; set; } - public int StepRemain { get; set; } - public int StepTotal { get; set; } - public int FailedAttempts { get; set; } - public ChallengePlatform Platform { get; set; } = ChallengePlatform.Unidentified; - 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(); - [MaxLength(128)] public string? IpAddress { get; set; } - [MaxLength(512)] public string? UserAgent { get; set; } - [MaxLength(256)] public string? DeviceId { get; set; } - [MaxLength(1024)] public string? Nonce { get; set; } - public Point? Location { get; set; } - - public Guid AccountId { get; set; } - [JsonIgnore] public Account.Account Account { get; set; } = null!; - - public Challenge Normalize() - { - if (StepRemain == 0 && BlacklistFactors.Count == 0) StepRemain = StepTotal; - return this; - } -} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Chat/ChatController.cs b/DysonNetwork.Sphere/Chat/ChatController.cs index 212592e..dc1a5db 100644 --- a/DysonNetwork.Sphere/Chat/ChatController.cs +++ b/DysonNetwork.Sphere/Chat/ChatController.cs @@ -1,7 +1,9 @@ using System.ComponentModel.DataAnnotations; using System.Text.RegularExpressions; +using DysonNetwork.Shared.Content; +using DysonNetwork.Shared.Data; +using DysonNetwork.Shared.Proto; using DysonNetwork.Sphere.Permission; -using DysonNetwork.Sphere.Storage; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; @@ -10,7 +12,12 @@ namespace DysonNetwork.Sphere.Chat; [ApiController] [Route("/api/chat")] -public partial class ChatController(AppDatabase db, ChatService cs, ChatRoomService crs) : ControllerBase +public partial class ChatController( + AppDatabase db, + ChatService cs, + ChatRoomService crs, + FileService.FileServiceClient files +) : ControllerBase { public class MarkMessageReadRequest { @@ -32,10 +39,11 @@ public partial class ChatController(AppDatabase db, ChatService cs, ChatRoomServ [Authorize] public async Task>> GetChatSummary() { - if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); - var unreadMessages = await cs.CountUnreadMessageForUser(currentUser.Id); - var lastMessages = await cs.ListLastMessageForUser(currentUser.Id); + var accountId = Guid.Parse(currentUser.Id); + var unreadMessages = await cs.CountUnreadMessageForUser(accountId); + var lastMessages = await cs.ListLastMessageForUser(accountId); var result = unreadMessages.Keys .Union(lastMessages.Keys) @@ -65,7 +73,7 @@ public partial class ChatController(AppDatabase db, ChatService cs, ChatRoomServ public async Task>> ListMessages(Guid roomId, [FromQuery] int offset, [FromQuery] int take = 20) { - var currentUser = HttpContext.Items["CurrentUser"] as Account.Account; + var currentUser = HttpContext.Items["CurrentUser"] as Account; var room = await db.ChatRooms.FirstOrDefaultAsync(r => r.Id == roomId); if (room is null) return NotFound(); @@ -74,8 +82,9 @@ public partial class ChatController(AppDatabase db, ChatService cs, ChatRoomServ { if (currentUser is null) return Unauthorized(); + var accountId = Guid.Parse(currentUser.Id); var member = await db.ChatMembers - .Where(m => m.AccountId == currentUser.Id && m.ChatRoomId == roomId) + .Where(m => m.AccountId == accountId && m.ChatRoomId == roomId) .FirstOrDefaultAsync(); if (member == null || member.Role < ChatMemberRole.Member) return StatusCode(403, "You are not a member of this chat room."); @@ -102,7 +111,7 @@ public partial class ChatController(AppDatabase db, ChatService cs, ChatRoomServ [HttpGet("{roomId:guid}/messages/{messageId:guid}")] public async Task> GetMessage(Guid roomId, Guid messageId) { - var currentUser = HttpContext.Items["CurrentUser"] as Account.Account; + var currentUser = HttpContext.Items["CurrentUser"] as Account; var room = await db.ChatRooms.FirstOrDefaultAsync(r => r.Id == roomId); if (room is null) return NotFound(); @@ -111,8 +120,9 @@ public partial class ChatController(AppDatabase db, ChatService cs, ChatRoomServ { if (currentUser is null) return Unauthorized(); + var accountId = Guid.Parse(currentUser.Id); var member = await db.ChatMembers - .Where(m => m.AccountId == currentUser.Id && m.ChatRoomId == roomId) + .Where(m => m.AccountId == accountId && m.ChatRoomId == roomId) .FirstOrDefaultAsync(); if (member == null || member.Role < ChatMemberRole.Member) return StatusCode(403, "You are not a member of this chat room."); @@ -139,14 +149,14 @@ public partial class ChatController(AppDatabase db, ChatService cs, ChatRoomServ [RequiredPermission("global", "chat.messages.create")] public async Task SendMessage([FromBody] SendMessageRequest request, Guid roomId) { - if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); request.Content = TextSanitizer.Sanitize(request.Content); if (string.IsNullOrWhiteSpace(request.Content) && (request.AttachmentsId == null || request.AttachmentsId.Count == 0)) return BadRequest("You cannot send an empty message."); - var member = await crs.GetRoomMember(currentUser.Id, roomId); + var member = await crs.GetRoomMember(Guid.Parse(currentUser.Id), roomId); if (member == null || member.Role < ChatMemberRole.Member) return StatusCode(403, "You need to be a normal member to send messages here."); @@ -162,12 +172,12 @@ public partial class ChatController(AppDatabase db, ChatService cs, ChatRoomServ message.Content = request.Content; if (request.AttachmentsId is not null) { - var attachments = await db.Files - .Where(f => request.AttachmentsId.Contains(f.Id)) - .ToListAsync(); - message.Attachments = attachments + var queryRequest = new GetFileBatchRequest(); + queryRequest.Ids.AddRange(request.AttachmentsId); + var queryResponse = await files.GetFileBatchAsync(queryRequest); + message.Attachments = queryResponse.Files .OrderBy(f => request.AttachmentsId.IndexOf(f.Id)) - .Select(f => f.ToReferenceObject()) + .Select(CloudFileReferenceObject.FromProtoValue) .ToList(); } @@ -216,7 +226,7 @@ public partial class ChatController(AppDatabase db, ChatService cs, ChatRoomServ [Authorize] public async Task UpdateMessage([FromBody] SendMessageRequest request, Guid roomId, Guid messageId) { - if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); request.Content = TextSanitizer.Sanitize(request.Content); @@ -229,7 +239,8 @@ public partial class ChatController(AppDatabase db, ChatService cs, ChatRoomServ if (message == null) return NotFound(); - if (message.Sender.AccountId != currentUser.Id) + var accountId = Guid.Parse(currentUser.Id); + if (message.Sender.AccountId != accountId) return StatusCode(403, "You can only edit your own messages."); if (string.IsNullOrWhiteSpace(request.Content) && @@ -269,7 +280,7 @@ public partial class ChatController(AppDatabase db, ChatService cs, ChatRoomServ [Authorize] public async Task DeleteMessage(Guid roomId, Guid messageId) { - if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); var message = await db.ChatMessages .Include(m => m.Sender) @@ -278,7 +289,8 @@ public partial class ChatController(AppDatabase db, ChatService cs, ChatRoomServ if (message == null) return NotFound(); - if (message.Sender.AccountId != currentUser.Id) + var accountId = Guid.Parse(currentUser.Id); + if (message.Sender.AccountId != accountId) return StatusCode(403, "You can only delete your own messages."); // Call service method to delete the message @@ -295,15 +307,16 @@ public partial class ChatController(AppDatabase db, ChatService cs, ChatRoomServ [HttpPost("{roomId:guid}/sync")] public async Task> GetSyncData([FromBody] SyncRequest request, Guid roomId) { - if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); + var accountId = Guid.Parse(currentUser.Id); var isMember = await db.ChatMembers - .AnyAsync(m => m.AccountId == currentUser.Id && m.ChatRoomId == roomId); + .AnyAsync(m => m.AccountId == accountId && m.ChatRoomId == roomId); if (!isMember) return StatusCode(403, "You are not a member of this chat room."); var response = await cs.GetSyncDataAsync(roomId, request.LastSyncTimestamp); return Ok(response); } -} +} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Chat/ChatRoom.cs b/DysonNetwork.Sphere/Chat/ChatRoom.cs index 5fd9d53..c37684c 100644 --- a/DysonNetwork.Sphere/Chat/ChatRoom.cs +++ b/DysonNetwork.Sphere/Chat/ChatRoom.cs @@ -1,7 +1,8 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using System.Text.Json.Serialization; -using DysonNetwork.Sphere.Storage; +using DysonNetwork.Shared.Data; +using DysonNetwork.Shared.Proto; using NodaTime; namespace DysonNetwork.Sphere.Chat; @@ -38,7 +39,7 @@ public class ChatRoom : ModelBase, IIdentifiedResource public ICollection DirectMembers { get; set; } = new List(); - public string ResourceIdentifier => $"chatroom/{Id}"; + public string ResourceIdentifier => $"chatroom:{Id}"; } public abstract class ChatMemberRole @@ -73,7 +74,7 @@ public class ChatMember : ModelBase public Guid ChatRoomId { get; set; } public ChatRoom ChatRoom { get; set; } = null!; public Guid AccountId { get; set; } - public Account.Account Account { get; set; } = null!; + public Account Account { get; set; } = null!; [MaxLength(1024)] public string? Nick { get; set; } @@ -105,7 +106,7 @@ public class ChatMemberTransmissionObject : ModelBase public Guid Id { get; set; } public Guid ChatRoomId { get; set; } public Guid AccountId { get; set; } - public Account.Account Account { get; set; } = null!; + public Account Account { get; set; } = null!; [MaxLength(1024)] public string? Nick { get; set; } diff --git a/DysonNetwork.Sphere/Chat/ChatRoomController.cs b/DysonNetwork.Sphere/Chat/ChatRoomController.cs index 8d8b0c4..f9cb452 100644 --- a/DysonNetwork.Sphere/Chat/ChatRoomController.cs +++ b/DysonNetwork.Sphere/Chat/ChatRoomController.cs @@ -1,11 +1,10 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using System.ComponentModel.DataAnnotations; -using DysonNetwork.Sphere.Account; +using DysonNetwork.Shared.Proto; using DysonNetwork.Sphere.Localization; using DysonNetwork.Sphere.Permission; using DysonNetwork.Sphere.Realm; -using DysonNetwork.Sphere.Storage; using Microsoft.AspNetCore.Authorization; using Microsoft.Extensions.Localization; using NodaTime; @@ -16,14 +15,12 @@ namespace DysonNetwork.Sphere.Chat; [Route("/api/chat")] public class ChatRoomController( AppDatabase db, - FileReferenceService fileRefService, ChatRoomService crs, RealmService rs, - ActionLogService als, - NotificationService nty, - RelationshipService rels, IStringLocalizer localizer, - AccountEventService aes + AccountService.AccountServiceClient accounts, + FileService.FileServiceClient files, + FileReferenceService.FileReferenceServiceClient fileRefs ) : ControllerBase { [HttpGet("{id:guid}")] @@ -36,8 +33,8 @@ public class ChatRoomController( if (chatRoom is null) return NotFound(); if (chatRoom.Type != ChatRoomType.DirectMessage) return Ok(chatRoom); - if (HttpContext.Items["CurrentUser"] is Account.Account currentUser) - chatRoom = await crs.LoadDirectMessageMembers(chatRoom, currentUser.Id); + if (HttpContext.Items["CurrentUser"] is Account currentUser) + chatRoom = await crs.LoadDirectMessageMembers(chatRoom, Guid.Parse(currentUser.Id)); return Ok(chatRoom); } @@ -46,18 +43,18 @@ public class ChatRoomController( [Authorize] public async Task>> ListJoinedChatRooms() { - if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); - var userId = currentUser.Id; + var accountId = Guid.Parse(currentUser.Id); var chatRooms = await db.ChatMembers - .Where(m => m.AccountId == userId) + .Where(m => m.AccountId == accountId) .Where(m => m.JoinedAt != null) .Where(m => m.LeaveAt == null) .Include(m => m.ChatRoom) .Select(m => m.ChatRoom) .ToListAsync(); - chatRooms = await crs.LoadDirectMessageMembers(chatRooms, userId); + chatRooms = await crs.LoadDirectMessageMembers(chatRooms, accountId); chatRooms = await crs.SortChatRoomByLastMessage(chatRooms); return Ok(chatRooms); @@ -72,21 +69,29 @@ public class ChatRoomController( [Authorize] public async Task> CreateDirectMessage([FromBody] DirectMessageRequest request) { - if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) + if (HttpContext.Items["CurrentUser"] is not Shared.Proto.Account currentUser) return Unauthorized(); - var relatedUser = await db.Accounts.FindAsync(request.RelatedUserId); + var relatedUser = await accounts.GetAccountAsync( + new GetAccountRequest { Id = request.RelatedUserId.ToString() } + ); if (relatedUser is null) return BadRequest("Related user was not found"); - if (await rels.HasRelationshipWithStatus(currentUser.Id, relatedUser.Id, RelationshipStatus.Blocked)) + var hasBlocked = await accounts.HasRelationshipAsync(new GetRelationshipRequest() + { + AccountId = currentUser.Id, + RelatedId = request.RelatedUserId.ToString(), + Status = -100 + }); + if (hasBlocked?.Value ?? false) return StatusCode(403, "You cannot create direct message with a user that blocked you."); // Check if DM already exists between these users var existingDm = await db.ChatRooms .Include(c => c.Members) .Where(c => c.Type == ChatRoomType.DirectMessage && c.Members.Count == 2) - .Where(c => c.Members.Any(m => m.AccountId == currentUser.Id)) + .Where(c => c.Members.Any(m => m.AccountId == Guid.Parse(currentUser.Id))) .Where(c => c.Members.Any(m => m.AccountId == request.RelatedUserId)) .FirstOrDefaultAsync(); @@ -102,9 +107,9 @@ public class ChatRoomController( { new() { - AccountId = currentUser.Id, + AccountId = Guid.Parse(currentUser.Id), Role = ChatMemberRole.Owner, - JoinedAt = NodaTime.Instant.FromDateTimeUtc(DateTime.UtcNow) + JoinedAt = Instant.FromDateTimeUtc(DateTime.UtcNow) }, new() { @@ -130,18 +135,18 @@ public class ChatRoomController( return Ok(dmRoom); } - [HttpGet("direct/{userId:guid}")] + [HttpGet("direct/{accountId:guid}")] [Authorize] - public async Task> GetDirectChatRoom(Guid userId) + public async Task> GetDirectChatRoom(Guid accountId) { - if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) + if (HttpContext.Items["CurrentUser"] is not Shared.Proto.Account currentUser) return Unauthorized(); var room = await db.ChatRooms .Include(c => c.Members) .Where(c => c.Type == ChatRoomType.DirectMessage && c.Members.Count == 2) - .Where(c => c.Members.Any(m => m.AccountId == currentUser.Id)) - .Where(c => c.Members.Any(m => m.AccountId == userId)) + .Where(c => c.Members.Any(m => m.AccountId == Guid.Parse(currentUser.Id))) + .Where(c => c.Members.Any(m => m.AccountId == accountId)) .FirstOrDefaultAsync(); if (room is null) return NotFound(); @@ -164,7 +169,7 @@ public class ChatRoomController( [RequiredPermission("global", "chat.create")] public async Task> CreateChatRoom(ChatRoomRequest request) { - if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); + if (HttpContext.Items["CurrentUser"] is not Shared.Proto.Account currentUser) return Unauthorized(); if (request.Name is null) return BadRequest("You cannot create a chat room without a name."); var chatRoom = new ChatRoom @@ -179,7 +184,7 @@ public class ChatRoomController( new() { Role = ChatMemberRole.Owner, - AccountId = currentUser.Id, + AccountId = Guid.Parse(currentUser.Id), JoinedAt = NodaTime.Instant.FromDateTimeUtc(DateTime.UtcNow) } } @@ -187,7 +192,8 @@ public class ChatRoomController( if (request.RealmId is not null) { - if (!await rs.IsMemberWithRole(request.RealmId.Value, currentUser.Id, RealmMemberRole.Moderator)) + if (!await rs.IsMemberWithRole(request.RealmId.Value, Guid.Parse(currentUser.Id), + RealmMemberRole.Moderator)) return StatusCode(403, "You need at least be a moderator to create chat linked to the realm."); chatRoom.RealmId = request.RealmId; } @@ -196,6 +202,14 @@ public class ChatRoomController( { chatRoom.Picture = (await db.Files.FindAsync(request.PictureId))?.ToReferenceObject(); if (chatRoom.Picture is null) return BadRequest("Invalid picture id, unable to find the file on cloud."); + await fileRefs.CreateReferenceAsync( + new CreateReferenceRequest + { + FileId = publisher.Picture.Id, + Usage = "publisher.picture", + ResourceId = publisher.ResourceIdentifier, + } + ); } if (request.BackgroundId is not null) @@ -236,7 +250,7 @@ public class ChatRoomController( [HttpPatch("{id:guid}")] public async Task> UpdateChatRoom(Guid id, [FromBody] ChatRoomRequest request) { - if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); + if (HttpContext.Items["CurrentUser"] is not Shared.Proto.Account currentUser) return Unauthorized(); var chatRoom = await db.ChatRooms .Where(e => e.Id == id) @@ -245,16 +259,17 @@ public class ChatRoomController( if (chatRoom.RealmId is not null) { - if (!await rs.IsMemberWithRole(chatRoom.RealmId.Value, currentUser.Id, RealmMemberRole.Moderator)) + if (!await rs.IsMemberWithRole(chatRoom.RealmId.Value, Guid.Parse(currentUser.Id), + RealmMemberRole.Moderator)) return StatusCode(403, "You need at least be a realm moderator to update the chat."); } - else if (!await crs.IsMemberWithRole(chatRoom.Id, currentUser.Id, ChatMemberRole.Moderator)) + else if (!await crs.IsMemberWithRole(chatRoom.Id, Guid.Parse(currentUser.Id), ChatMemberRole.Moderator)) return StatusCode(403, "You need at least be a moderator to update the chat."); if (request.RealmId is not null) { var member = await db.RealmMembers - .Where(m => m.AccountId == currentUser.Id) + .Where(m => m.AccountId == Guid.Parse(currentUser.Id)) .Where(m => m.RealmId == request.RealmId) .FirstOrDefaultAsync(); if (member is null || member.Role < RealmMemberRole.Moderator) @@ -321,7 +336,7 @@ public class ChatRoomController( [HttpDelete("{id:guid}")] public async Task DeleteChatRoom(Guid id) { - if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); + if (HttpContext.Items["CurrentUser"] is not Shared.Proto.Account currentUser) return Unauthorized(); var chatRoom = await db.ChatRooms .Where(e => e.Id == id) @@ -330,10 +345,11 @@ public class ChatRoomController( if (chatRoom.RealmId is not null) { - if (!await rs.IsMemberWithRole(chatRoom.RealmId.Value, currentUser.Id, RealmMemberRole.Moderator)) + if (!await rs.IsMemberWithRole(chatRoom.RealmId.Value, Guid.Parse(currentUser.Id), + RealmMemberRole.Moderator)) return StatusCode(403, "You need at least be a realm moderator to delete the chat."); } - else if (!await crs.IsMemberWithRole(chatRoom.Id, currentUser.Id, ChatMemberRole.Owner)) + else if (!await crs.IsMemberWithRole(chatRoom.Id, Guid.Parse(currentUser.Id), ChatMemberRole.Owner)) return StatusCode(403, "You need at least be the owner to delete the chat."); var chatRoomResourceId = $"chatroom:{chatRoom.Id}"; @@ -356,11 +372,11 @@ public class ChatRoomController( [Authorize] public async Task> GetRoomIdentity(Guid roomId) { - if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) + if (HttpContext.Items["CurrentUser"] is not Shared.Proto.Account currentUser) return Unauthorized(); var member = await db.ChatMembers - .Where(m => m.AccountId == currentUser.Id && m.ChatRoomId == roomId) + .Where(m => m.AccountId == Guid.Parse(currentUser.Id) && m.ChatRoomId == roomId) .Include(m => m.Account) .Include(m => m.Account.Profile) .FirstOrDefaultAsync(); @@ -375,7 +391,7 @@ public class ChatRoomController( public async Task>> ListMembers(Guid roomId, [FromQuery] int take = 20, [FromQuery] int skip = 0, [FromQuery] bool withStatus = false, [FromQuery] string? status = null) { - var currentUser = HttpContext.Items["CurrentUser"] as Account.Account; + var currentUser = HttpContext.Items["CurrentUser"] as Shared.Proto.Account; var room = await db.ChatRooms .FirstOrDefaultAsync(r => r.Id == roomId); @@ -385,7 +401,7 @@ public class ChatRoomController( { if (currentUser is null) return Unauthorized(); var member = await db.ChatMembers - .FirstOrDefaultAsync(m => m.ChatRoomId == roomId && m.AccountId == currentUser.Id); + .FirstOrDefaultAsync(m => m.ChatRoomId == roomId && m.AccountId == Guid.Parse(currentUser.Id)); if (member is null) return StatusCode(403, "You need to be a member to see members of private chat room."); } @@ -435,7 +451,6 @@ public class ChatRoomController( } } - public class ChatMemberRequest { @@ -448,13 +463,14 @@ public class ChatRoomController( public async Task> InviteMember(Guid roomId, [FromBody] ChatMemberRequest request) { - if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); - var userId = currentUser.Id; + if (HttpContext.Items["CurrentUser"] is not Shared.Proto.Account currentUser) return Unauthorized(); + var accountId = Guid.Parse(currentUser.Id); var relatedUser = await db.Accounts.FindAsync(request.RelatedUserId); if (relatedUser is null) return BadRequest("Related user was not found"); - if (await rels.HasRelationshipWithStatus(currentUser.Id, relatedUser.Id, RelationshipStatus.Blocked)) + if (await rels.HasRelationshipWithStatus(Guid.Parse(currentUser.Id), relatedUser.Id, + RelationshipStatus.Blocked)) return StatusCode(403, "You cannot invite a user that blocked you."); var chatRoom = await db.ChatRooms @@ -466,7 +482,7 @@ public class ChatRoomController( if (chatRoom.RealmId is not null) { var realmMember = await db.RealmMembers - .Where(m => m.AccountId == userId) + .Where(m => m.AccountId == accountId) .Where(m => m.RealmId == chatRoom.RealmId) .FirstOrDefaultAsync(); if (realmMember is null || realmMember.Role < RealmMemberRole.Moderator) @@ -475,7 +491,7 @@ public class ChatRoomController( else { var chatMember = await db.ChatMembers - .Where(m => m.AccountId == userId) + .Where(m => m.AccountId == accountId) .Where(m => m.ChatRoomId == roomId) .FirstOrDefaultAsync(); if (chatMember is null) return StatusCode(403, "You are not even a member of the targeted chat room."); @@ -519,11 +535,11 @@ public class ChatRoomController( [Authorize] public async Task>> ListChatInvites() { - if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); - var userId = currentUser.Id; + if (HttpContext.Items["CurrentUser"] is not Shared.Proto.Account currentUser) return Unauthorized(); + var accountId = Guid.Parse(currentUser.Id); var members = await db.ChatMembers - .Where(m => m.AccountId == userId) + .Where(m => m.AccountId == accountId) .Where(m => m.JoinedAt == null) .Include(e => e.ChatRoom) .Include(e => e.Account) @@ -532,7 +548,7 @@ public class ChatRoomController( var chatRooms = members.Select(m => m.ChatRoom).ToList(); var directMembers = - (await crs.LoadDirectMessageMembers(chatRooms, userId)).ToDictionary(c => c.Id, c => c.Members); + (await crs.LoadDirectMessageMembers(chatRooms, accountId)).ToDictionary(c => c.Id, c => c.Members); foreach (var member in members.Where(member => member.ChatRoom.Type == ChatRoomType.DirectMessage)) member.ChatRoom.Members = directMembers[member.ChatRoom.Id]; @@ -544,11 +560,11 @@ public class ChatRoomController( [Authorize] public async Task> AcceptChatInvite(Guid roomId) { - if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); - var userId = currentUser.Id; + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); + var accountId = Guid.Parse(currentUser.Id); var member = await db.ChatMembers - .Where(m => m.AccountId == userId) + .Where(m => m.AccountId == accountId) .Where(m => m.ChatRoomId == roomId) .Where(m => m.JoinedAt == null) .FirstOrDefaultAsync(); @@ -571,11 +587,11 @@ public class ChatRoomController( [Authorize] public async Task DeclineChatInvite(Guid roomId) { - if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); - var userId = currentUser.Id; + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); + var accountId = Guid.Parse(currentUser.Id); var member = await db.ChatMembers - .Where(m => m.AccountId == userId) + .Where(m => m.AccountId == accountId) .Where(m => m.ChatRoomId == roomId) .Where(m => m.JoinedAt == null) .FirstOrDefaultAsync(); @@ -600,15 +616,16 @@ public class ChatRoomController( [FromBody] ChatMemberNotifyRequest request ) { - if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); var chatRoom = await db.ChatRooms .Where(r => r.Id == roomId) .FirstOrDefaultAsync(); if (chatRoom is null) return NotFound(); + var accountId = Guid.Parse(currentUser.Id); var targetMember = await db.ChatMembers - .Where(m => m.AccountId == currentUser.Id && m.ChatRoomId == roomId) + .Where(m => m.AccountId == accountId && m.ChatRoomId == roomId) .FirstOrDefaultAsync(); if (targetMember is null) return BadRequest("You have not joined this chat room."); if (request.NotifyLevel is not null) @@ -629,7 +646,7 @@ public class ChatRoomController( public async Task> UpdateChatMemberRole(Guid roomId, Guid memberId, [FromBody] int newRole) { if (newRole >= ChatMemberRole.Owner) return BadRequest("Unable to set chat member to owner or greater role."); - if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); var chatRoom = await db.ChatRooms .Where(r => r.Id == roomId) @@ -640,7 +657,7 @@ public class ChatRoomController( if (chatRoom.RealmId is not null) { var realmMember = await db.RealmMembers - .Where(m => m.AccountId == currentUser.Id) + .Where(m => m.AccountId == Guid.Parse(currentUser.Id)) .Where(m => m.RealmId == chatRoom.RealmId) .FirstOrDefaultAsync(); if (realmMember is null || realmMember.Role < RealmMemberRole.Moderator) @@ -657,7 +674,7 @@ public class ChatRoomController( if ( !await crs.IsMemberWithRole( chatRoom.Id, - currentUser.Id, + Guid.Parse(currentUser.Id), ChatMemberRole.Moderator, targetMember.Role, newRole @@ -688,7 +705,7 @@ public class ChatRoomController( [Authorize] public async Task RemoveChatMember(Guid roomId, Guid memberId) { - if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); var chatRoom = await db.ChatRooms .Where(r => r.Id == roomId) @@ -698,12 +715,13 @@ public class ChatRoomController( // Check if the chat room is owned by a realm if (chatRoom.RealmId is not null) { - if (!await rs.IsMemberWithRole(chatRoom.RealmId.Value, currentUser.Id, RealmMemberRole.Moderator)) + if (!await rs.IsMemberWithRole(chatRoom.RealmId.Value, Guid.Parse(currentUser.Id), + RealmMemberRole.Moderator)) return StatusCode(403, "You need at least be a realm moderator to remove members."); } else { - if (!await crs.IsMemberWithRole(chatRoom.Id, currentUser.Id, ChatMemberRole.Moderator)) + if (!await crs.IsMemberWithRole(chatRoom.Id, Guid.Parse(currentUser.Id), ChatMemberRole.Moderator)) return StatusCode(403, "You need at least be a moderator to remove members."); // Find the target member @@ -736,7 +754,7 @@ public class ChatRoomController( [Authorize] public async Task> JoinChatRoom(Guid roomId) { - if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); var chatRoom = await db.ChatRooms .Where(r => r.Id == roomId) @@ -746,13 +764,13 @@ public class ChatRoomController( return StatusCode(403, "This chat room isn't a community. You need an invitation to join."); var existingMember = await db.ChatMembers - .FirstOrDefaultAsync(m => m.AccountId == currentUser.Id && m.ChatRoomId == roomId); + .FirstOrDefaultAsync(m => m.AccountId == Guid.Parse(currentUser.Id) && m.ChatRoomId == roomId); if (existingMember != null) return BadRequest("You are already a member of this chat room."); var newMember = new ChatMember { - AccountId = currentUser.Id, + AccountId = Guid.Parse(currentUser.Id), ChatRoomId = roomId, Role = ChatMemberRole.Member, JoinedAt = NodaTime.Instant.FromDateTimeUtc(DateTime.UtcNow) @@ -774,10 +792,10 @@ public class ChatRoomController( [Authorize] public async Task LeaveChat(Guid roomId) { - if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); var member = await db.ChatMembers - .Where(m => m.AccountId == currentUser.Id) + .Where(m => m.AccountId == Guid.Parse(currentUser.Id)) .Where(m => m.ChatRoomId == roomId) .FirstOrDefaultAsync(); if (member is null) return NotFound(); @@ -788,7 +806,7 @@ public class ChatRoomController( var otherOwners = await db.ChatMembers .Where(m => m.ChatRoomId == roomId) .Where(m => m.Role == ChatMemberRole.Owner) - .Where(m => m.AccountId != currentUser.Id) + .Where(m => m.AccountId != Guid.Parse(currentUser.Id)) .AnyAsync(); if (!otherOwners) @@ -807,7 +825,7 @@ public class ChatRoomController( return NoContent(); } - private async Task _SendInviteNotify(ChatMember member, Account.Account sender) + private async Task _SendInviteNotify(ChatMember member, Account sender) { string title = localizer["ChatInviteTitle"]; diff --git a/DysonNetwork.Sphere/Chat/ChatRoomService.cs b/DysonNetwork.Sphere/Chat/ChatRoomService.cs index be57a69..1e23d19 100644 --- a/DysonNetwork.Sphere/Chat/ChatRoomService.cs +++ b/DysonNetwork.Sphere/Chat/ChatRoomService.cs @@ -1,4 +1,4 @@ -using DysonNetwork.Sphere.Storage; +using DysonNetwork.Shared.Cache; using Microsoft.EntityFrameworkCore; using NodaTime; diff --git a/DysonNetwork.Sphere/Chat/ChatService.cs b/DysonNetwork.Sphere/Chat/ChatService.cs index 1ff92ed..bdad2d9 100644 --- a/DysonNetwork.Sphere/Chat/ChatService.cs +++ b/DysonNetwork.Sphere/Chat/ChatService.cs @@ -241,7 +241,7 @@ public partial class ChatService( Priority = 10, }; - List accountsToNotify = []; + List accountsToNotify = []; foreach (var member in members) { scopedWs.SendPacketToAccount(member.AccountId, new WebSocketPacket diff --git a/DysonNetwork.Sphere/Chat/Message.cs b/DysonNetwork.Sphere/Chat/Message.cs index a0ef968..9865823 100644 --- a/DysonNetwork.Sphere/Chat/Message.cs +++ b/DysonNetwork.Sphere/Chat/Message.cs @@ -1,8 +1,7 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using System.Text.Json.Serialization; -using DysonNetwork.Sphere.Storage; -using Microsoft.EntityFrameworkCore; +using DysonNetwork.Shared.Data; using NodaTime; namespace DysonNetwork.Sphere.Chat; @@ -19,8 +18,6 @@ public class Message : ModelBase, IIdentifiedResource [Column(TypeName = "jsonb")] public List Attachments { get; set; } = []; - // Outdated fields, keep for backward compability - public ICollection OutdatedAttachments { get; set; } = new List(); public ICollection Reactions { get; set; } = new List(); public Guid? RepliedMessageId { get; set; } @@ -33,7 +30,7 @@ public class Message : ModelBase, IIdentifiedResource public Guid ChatRoomId { get; set; } [JsonIgnore] public ChatRoom ChatRoom { get; set; } = null!; - public string ResourceIdentifier => $"message/{Id}"; + public string ResourceIdentifier => $"message:{Id}"; } public enum MessageReactionAttitude diff --git a/DysonNetwork.Sphere/Chat/Realtime/IRealtimeService.cs b/DysonNetwork.Sphere/Chat/Realtime/IRealtimeService.cs index b7c7509..cd34f8b 100644 --- a/DysonNetwork.Sphere/Chat/Realtime/IRealtimeService.cs +++ b/DysonNetwork.Sphere/Chat/Realtime/IRealtimeService.cs @@ -36,7 +36,7 @@ public interface IRealtimeService /// The session identifier /// The user is the admin of session /// User-specific token for the session - string GetUserToken(Account.Account account, string sessionId, bool isAdmin = false); + string GetUserToken(Account account, string sessionId, bool isAdmin = false); /// /// Processes incoming webhook requests from the realtime service provider diff --git a/DysonNetwork.Sphere/Chat/Realtime/LivekitService.cs b/DysonNetwork.Sphere/Chat/Realtime/LivekitService.cs index 8e07d05..4313910 100644 --- a/DysonNetwork.Sphere/Chat/Realtime/LivekitService.cs +++ b/DysonNetwork.Sphere/Chat/Realtime/LivekitService.cs @@ -111,7 +111,7 @@ public class LivekitRealtimeService : IRealtimeService } /// - public string GetUserToken(Account.Account account, string sessionId, bool isAdmin = false) + public string GetUserToken(Account account, string sessionId, bool isAdmin = false) { var token = _accessToken.WithIdentity(account.Name) .WithName(account.Nick) diff --git a/DysonNetwork.Sphere/Chat/RealtimeCallController.cs b/DysonNetwork.Sphere/Chat/RealtimeCallController.cs index e26bcd9..6d300a2 100644 --- a/DysonNetwork.Sphere/Chat/RealtimeCallController.cs +++ b/DysonNetwork.Sphere/Chat/RealtimeCallController.cs @@ -46,7 +46,7 @@ public class RealtimeCallController( [Authorize] public async Task> GetOngoingCall(Guid roomId) { - if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); var member = await db.ChatMembers .Where(m => m.AccountId == currentUser.Id && m.ChatRoomId == roomId) @@ -71,7 +71,7 @@ public class RealtimeCallController( [Authorize] public async Task> JoinCall(Guid roomId) { - if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); // Check if the user is a member of the chat room var member = await db.ChatMembers @@ -144,7 +144,7 @@ public class RealtimeCallController( [Authorize] public async Task> StartCall(Guid roomId) { - if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); var member = await db.ChatMembers .Where(m => m.AccountId == currentUser.Id && m.ChatRoomId == roomId) @@ -163,7 +163,7 @@ public class RealtimeCallController( [Authorize] public async Task> EndCall(Guid roomId) { - if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); var member = await db.ChatMembers .Where(m => m.AccountId == currentUser.Id && m.ChatRoomId == roomId) diff --git a/DysonNetwork.Sphere/Connection/AutoCompletionController.cs b/DysonNetwork.Sphere/Connection/AutoCompletionController.cs deleted file mode 100644 index 94eae1c..0000000 --- a/DysonNetwork.Sphere/Connection/AutoCompletionController.cs +++ /dev/null @@ -1,92 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; - -namespace DysonNetwork.Sphere.Connection; - -[ApiController] -[Route("completion")] -public class AutoCompletionController(AppDatabase db) - : ControllerBase -{ - [HttpPost] - public async Task> GetCompletions([FromBody] AutoCompletionRequest request) - { - if (string.IsNullOrWhiteSpace(request?.Content)) - { - return BadRequest("Content is required"); - } - - var result = new AutoCompletionResponse(); - var lastWord = request.Content.Trim().Split(' ').LastOrDefault() ?? string.Empty; - - if (lastWord.StartsWith("@")) - { - var searchTerm = lastWord[1..]; // Remove the @ - result.Items = await GetAccountCompletions(searchTerm); - result.Type = "account"; - } - else if (lastWord.StartsWith(":")) - { - var searchTerm = lastWord[1..]; // Remove the : - result.Items = await GetStickerCompletions(searchTerm); - result.Type = "sticker"; - } - - return Ok(result); - } - - private async Task> GetAccountCompletions(string searchTerm) - { - return await db.Accounts - .Where(a => EF.Functions.ILike(a.Name, $"%{searchTerm}%")) - .OrderBy(a => a.Name) - .Take(10) - .Select(a => new CompletionItem - { - Id = a.Id.ToString(), - DisplayName = a.Name, - SecondaryText = a.Nick, - Type = "account", - Data = a - }) - .ToListAsync(); - } - - private async Task> GetStickerCompletions(string searchTerm) - { - return await db.Stickers - .Include(s => s.Pack) - .Where(s => EF.Functions.ILike(s.Pack.Prefix + s.Slug, $"%{searchTerm}%")) - .OrderBy(s => s.Slug) - .Take(10) - .Select(s => new CompletionItem - { - Id = s.Id.ToString(), - DisplayName = s.Slug, - Type = "sticker", - Data = s - }) - .ToListAsync(); - } -} - -public class AutoCompletionRequest -{ - [Required] public string Content { get; set; } = string.Empty; -} - -public class AutoCompletionResponse -{ - public string Type { get; set; } = string.Empty; - public List Items { get; set; } = new(); -} - -public class CompletionItem -{ - public string Id { get; set; } = string.Empty; - public string DisplayName { get; set; } = string.Empty; - public string? SecondaryText { get; set; } - public string Type { get; set; } = string.Empty; - public object? Data { get; set; } -} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Connection/ClientTypeMiddleware.cs b/DysonNetwork.Sphere/Connection/ClientTypeMiddleware.cs deleted file mode 100644 index 8a305aa..0000000 --- a/DysonNetwork.Sphere/Connection/ClientTypeMiddleware.cs +++ /dev/null @@ -1,42 +0,0 @@ -namespace DysonNetwork.Sphere.Connection; - -public class ClientTypeMiddleware(RequestDelegate next) -{ - public async Task Invoke(HttpContext context) - { - var headers = context.Request.Headers; - bool isWebPage; - - // Priority 1: Check for custom header - if (headers.TryGetValue("X-Client", out var clientType)) - { - isWebPage = clientType.ToString().Length == 0; - } - else - { - var userAgent = headers.UserAgent.ToString(); - var accept = headers.Accept.ToString(); - - // Priority 2: Check known app User-Agent (backward compatibility) - if (!string.IsNullOrEmpty(userAgent) && userAgent.Contains("Solian")) - isWebPage = false; - // Priority 3: Accept header can help infer intent - else if (!string.IsNullOrEmpty(accept) && accept.Contains("text/html")) - isWebPage = true; - else if (!string.IsNullOrEmpty(accept) && accept.Contains("application/json")) - isWebPage = false; - else - isWebPage = true; - } - - context.Items["IsWebPage"] = isWebPage; - - if (!isWebPage && context.Request.Path != "/ws" && !context.Request.Path.StartsWithSegments("/api")) - context.Response.Redirect( - $"/api{context.Request.Path.Value}{context.Request.QueryString.Value}", - permanent: false - ); - else - await next(context); - } -} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Connection/GeoIpService.cs b/DysonNetwork.Sphere/Connection/GeoIpService.cs deleted file mode 100644 index c4ef31d..0000000 --- a/DysonNetwork.Sphere/Connection/GeoIpService.cs +++ /dev/null @@ -1,56 +0,0 @@ -using MaxMind.GeoIP2; -using NetTopologySuite.Geometries; -using Microsoft.Extensions.Options; -using Point = NetTopologySuite.Geometries.Point; - -namespace DysonNetwork.Sphere.Connection; - -public class GeoIpOptions -{ - public string DatabasePath { get; set; } = null!; -} - -public class GeoIpService(IOptions options) -{ - private readonly string _databasePath = options.Value.DatabasePath; - private readonly GeometryFactory _geometryFactory = new(new PrecisionModel(), 4326); // 4326 is the SRID for WGS84 - - public Point? GetPointFromIp(string? ipAddress) - { - if (string.IsNullOrEmpty(ipAddress)) - return null; - - try - { - using var reader = new DatabaseReader(_databasePath); - var city = reader.City(ipAddress); - - if (city?.Location == null || !city.Location.HasCoordinates) - return null; - - return _geometryFactory.CreatePoint(new Coordinate( - city.Location.Longitude ?? 0, - city.Location.Latitude ?? 0)); - } - catch (Exception) - { - return null; - } - } - - public MaxMind.GeoIP2.Responses.CityResponse? GetFromIp(string? ipAddress) - { - if (string.IsNullOrEmpty(ipAddress)) - return null; - - try - { - using var reader = new DatabaseReader(_databasePath); - return reader.City(ipAddress); - } - catch (Exception) - { - return null; - } - } -} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Connection/Handlers/MessageReadHandler.cs b/DysonNetwork.Sphere/Connection/Handlers/MessageReadHandler.cs deleted file mode 100644 index ee072d2..0000000 --- a/DysonNetwork.Sphere/Connection/Handlers/MessageReadHandler.cs +++ /dev/null @@ -1,68 +0,0 @@ -using System.Net.WebSockets; -using DysonNetwork.Sphere.Chat; -using DysonNetwork.Sphere.Storage; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Caching.Memory; -using Microsoft.Extensions.Internal; -using SystemClock = NodaTime.SystemClock; - -namespace DysonNetwork.Sphere.Connection.Handlers; - -public class MessageReadHandler( - ChatRoomService crs, - FlushBufferService buffer -) - : IWebSocketPacketHandler -{ - public string PacketType => "messages.read"; - - public const string ChatMemberCacheKey = "ChatMember_{0}_{1}"; - - public async Task HandleAsync( - Account.Account currentUser, - string deviceId, - WebSocketPacket packet, - WebSocket socket, - WebSocketService srv - ) - { - var request = packet.GetData(); - if (request is null) - { - await socket.SendAsync( - new ArraySegment(new WebSocketPacket - { - Type = WebSocketPacketType.Error, - ErrorMessage = "Mark message as read requires you provide the ChatRoomId and MessageId" - }.ToBytes()), - WebSocketMessageType.Binary, - true, - CancellationToken.None - ); - return; - } - - var sender = await crs.GetRoomMember(currentUser.Id, request.ChatRoomId); - if (sender is null) - { - await socket.SendAsync( - new ArraySegment(new WebSocketPacket - { - Type = WebSocketPacketType.Error, - ErrorMessage = "User is not a member of the chat room." - }.ToBytes()), - WebSocketMessageType.Binary, - true, - CancellationToken.None - ); - return; - } - - var readReceipt = new MessageReadReceipt - { - SenderId = sender.Id, - }; - - buffer.Enqueue(readReceipt); - } -} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Connection/Handlers/MessageTypingHandler.cs b/DysonNetwork.Sphere/Connection/Handlers/MessageTypingHandler.cs deleted file mode 100644 index b6447db..0000000 --- a/DysonNetwork.Sphere/Connection/Handlers/MessageTypingHandler.cs +++ /dev/null @@ -1,68 +0,0 @@ -using System.Net.WebSockets; -using DysonNetwork.Sphere.Chat; -using DysonNetwork.Sphere.Storage; -using Microsoft.EntityFrameworkCore; - -namespace DysonNetwork.Sphere.Connection.Handlers; - -public class MessageTypingHandler(ChatRoomService crs) : IWebSocketPacketHandler -{ - public string PacketType => "messages.typing"; - - public async Task HandleAsync( - Account.Account currentUser, - string deviceId, - WebSocketPacket packet, - WebSocket socket, - WebSocketService srv - ) - { - var request = packet.GetData(); - if (request is null) - { - await socket.SendAsync( - new ArraySegment(new WebSocketPacket - { - Type = WebSocketPacketType.Error, - ErrorMessage = "Mark message as read requires you provide the ChatRoomId" - }.ToBytes()), - WebSocketMessageType.Binary, - true, - CancellationToken.None - ); - return; - } - - var sender = await crs.GetRoomMember(currentUser.Id, request.ChatRoomId); - if (sender is null) - { - await socket.SendAsync( - new ArraySegment(new WebSocketPacket - { - Type = WebSocketPacketType.Error, - ErrorMessage = "User is not a member of the chat room." - }.ToBytes()), - WebSocketMessageType.Binary, - true, - CancellationToken.None - ); - return; - } - - var responsePacket = new WebSocketPacket - { - Type = "messages.typing", - Data = new Dictionary() - { - ["room_id"] = sender.ChatRoomId, - ["sender_id"] = sender.Id, - ["sender"] = sender - } - }; - - // Broadcast read statuses - var otherMembers = (await crs.ListRoomMembers(request.ChatRoomId)).Select(m => m.AccountId).ToList(); - foreach (var member in otherMembers) - srv.SendPacketToAccount(member, responsePacket); - } -} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Connection/Handlers/MessagesSubscribeHandler.cs b/DysonNetwork.Sphere/Connection/Handlers/MessagesSubscribeHandler.cs deleted file mode 100644 index cf5792b..0000000 --- a/DysonNetwork.Sphere/Connection/Handlers/MessagesSubscribeHandler.cs +++ /dev/null @@ -1,53 +0,0 @@ - -using System.Net.WebSockets; -using DysonNetwork.Sphere.Chat; - -namespace DysonNetwork.Sphere.Connection.Handlers; - -public class MessagesSubscribeHandler(ChatRoomService crs) : IWebSocketPacketHandler -{ - public string PacketType => "messages.subscribe"; - - public async Task HandleAsync( - Account.Account currentUser, - string deviceId, - WebSocketPacket packet, - WebSocket socket, - WebSocketService srv - ) - { - var request = packet.GetData(); - if (request is null) - { - await socket.SendAsync( - new ArraySegment(new WebSocketPacket - { - Type = WebSocketPacketType.Error, - ErrorMessage = "messages.subscribe requires you provide the ChatRoomId" - }.ToBytes()), - WebSocketMessageType.Binary, - true, - CancellationToken.None - ); - return; - } - - var sender = await crs.GetRoomMember(currentUser.Id, request.ChatRoomId); - if (sender is null) - { - await socket.SendAsync( - new ArraySegment(new WebSocketPacket - { - Type = WebSocketPacketType.Error, - ErrorMessage = "User is not a member of the chat room." - }.ToBytes()), - WebSocketMessageType.Binary, - true, - CancellationToken.None - ); - return; - } - - srv.SubscribeToChatRoom(sender.ChatRoomId.ToString(), deviceId); - } -} diff --git a/DysonNetwork.Sphere/Connection/Handlers/MessagesUnsubscribeHandler.cs b/DysonNetwork.Sphere/Connection/Handlers/MessagesUnsubscribeHandler.cs deleted file mode 100644 index cc507ac..0000000 --- a/DysonNetwork.Sphere/Connection/Handlers/MessagesUnsubscribeHandler.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Net.WebSockets; -using DysonNetwork.Sphere.Chat; - -namespace DysonNetwork.Sphere.Connection.Handlers; - -public class MessagesUnsubscribeHandler() : IWebSocketPacketHandler -{ - public string PacketType => "messages.unsubscribe"; - - public Task HandleAsync( - Account.Account currentUser, - string deviceId, - WebSocketPacket packet, - WebSocket socket, - WebSocketService srv - ) - { - srv.UnsubscribeFromChatRoom(deviceId); - return Task.CompletedTask; - } -} diff --git a/DysonNetwork.Sphere/Connection/IWebSocketPacketHandler.cs b/DysonNetwork.Sphere/Connection/IWebSocketPacketHandler.cs deleted file mode 100644 index b625a01..0000000 --- a/DysonNetwork.Sphere/Connection/IWebSocketPacketHandler.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Net.WebSockets; - -namespace DysonNetwork.Sphere.Connection; - -public interface IWebSocketPacketHandler -{ - string PacketType { get; } - Task HandleAsync(Account.Account currentUser, string deviceId, WebSocketPacket packet, WebSocket socket, WebSocketService srv); -} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Connection/WebSocketController.cs b/DysonNetwork.Sphere/Connection/WebSocketController.cs deleted file mode 100644 index 3bf61cf..0000000 --- a/DysonNetwork.Sphere/Connection/WebSocketController.cs +++ /dev/null @@ -1,108 +0,0 @@ -using System.Collections.Concurrent; -using System.Net.WebSockets; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore.Metadata.Internal; -using Swashbuckle.AspNetCore.Annotations; - -namespace DysonNetwork.Sphere.Connection; - -[ApiController] -[Route("/ws")] -public class WebSocketController(WebSocketService ws, ILogger logger) : ControllerBase -{ - [Route("/ws")] - [Authorize] - [SwaggerIgnore] - public async Task TheGateway() - { - HttpContext.Items.TryGetValue("CurrentUser", out var currentUserValue); - HttpContext.Items.TryGetValue("CurrentSession", out var currentSessionValue); - if (currentUserValue is not Account.Account currentUser || - currentSessionValue is not Auth.Session currentSession) - { - HttpContext.Response.StatusCode = StatusCodes.Status401Unauthorized; - return; - } - - var accountId = currentUser.Id; - var deviceId = currentSession.Challenge.DeviceId; - - if (string.IsNullOrEmpty(deviceId)) - { - HttpContext.Response.StatusCode = StatusCodes.Status400BadRequest; - return; - } - - using var webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync(); - var cts = new CancellationTokenSource(); - var connectionKey = (accountId, deviceId); - - if (!ws.TryAdd(connectionKey, webSocket, cts)) - { - await webSocket.CloseAsync( - WebSocketCloseStatus.InternalServerError, - "Failed to establish connection.", - CancellationToken.None - ); - return; - } - - logger.LogInformation( - $"Connection established with user @{currentUser.Name}#{currentUser.Id} and device #{deviceId}"); - - try - { - await _ConnectionEventLoop(deviceId, currentUser, webSocket, cts.Token); - } - catch (Exception ex) - { - Console.WriteLine($"WebSocket Error: {ex.Message}"); - } - finally - { - ws.Disconnect(connectionKey); - logger.LogInformation( - $"Connection disconnected with user @{currentUser.Name}#{currentUser.Id} and device #{deviceId}"); - } - } - - private async Task _ConnectionEventLoop( - string deviceId, - Account.Account currentUser, - WebSocket webSocket, - CancellationToken cancellationToken - ) - { - var connectionKey = (AccountId: currentUser.Id, DeviceId: deviceId); - - var buffer = new byte[1024 * 4]; - try - { - var receiveResult = await webSocket.ReceiveAsync( - new ArraySegment(buffer), - cancellationToken - ); - while (!receiveResult.CloseStatus.HasValue) - { - receiveResult = await webSocket.ReceiveAsync( - new ArraySegment(buffer), - cancellationToken - ); - - var packet = WebSocketPacket.FromBytes(buffer[..receiveResult.Count]); - _ = ws.HandlePacket(currentUser, connectionKey.DeviceId, packet, webSocket); - } - } - catch (OperationCanceledException) - { - if ( - webSocket.State != WebSocketState.Closed - && webSocket.State != WebSocketState.Aborted - ) - { - ws.Disconnect(connectionKey); - } - } - } -} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Connection/WebSocketPacket.cs b/DysonNetwork.Sphere/Connection/WebSocketPacket.cs deleted file mode 100644 index 745a961..0000000 --- a/DysonNetwork.Sphere/Connection/WebSocketPacket.cs +++ /dev/null @@ -1,72 +0,0 @@ -using System.Text.Json; -using NodaTime; -using NodaTime.Serialization.SystemTextJson; - -public class WebSocketPacketType -{ - public const string Error = "error"; - public const string MessageNew = "messages.new"; - public const string MessageUpdate = "messages.update"; - public const string MessageDelete = "messages.delete"; - public const string CallParticipantsUpdate = "call.participants.update"; -} - -public class WebSocketPacket -{ - public string Type { get; set; } = null!; - public object Data { get; set; } = null!; - public string? ErrorMessage { get; set; } - - /// - /// Creates a WebSocketPacket from raw WebSocket message bytes - /// - /// Raw WebSocket message bytes - /// Deserialized WebSocketPacket - public static WebSocketPacket FromBytes(byte[] bytes) - { - var json = System.Text.Encoding.UTF8.GetString(bytes); - var jsonOpts = new JsonSerializerOptions - { - PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower, - DictionaryKeyPolicy = JsonNamingPolicy.SnakeCaseLower, - }; - return JsonSerializer.Deserialize(json, jsonOpts) ?? - throw new JsonException("Failed to deserialize WebSocketPacket"); - } - - /// - /// Deserializes the Data property to the specified type T - /// - /// Target type to deserialize to - /// Deserialized data of type T - public T? GetData() - { - if (Data is T typedData) - return typedData; - - var jsonOpts = new JsonSerializerOptions - { - PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower, - DictionaryKeyPolicy = JsonNamingPolicy.SnakeCaseLower, - }; - return JsonSerializer.Deserialize( - JsonSerializer.Serialize(Data, jsonOpts), - jsonOpts - ); - } - - /// - /// Serializes this WebSocketPacket to a byte array for sending over WebSocket - /// - /// Byte array representation of the packet - public byte[] ToBytes() - { - var jsonOpts = new JsonSerializerOptions - { - PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower, - DictionaryKeyPolicy = JsonNamingPolicy.SnakeCaseLower, - }.ConfigureForNodaTime(DateTimeZoneProviders.Tzdb); - var json = JsonSerializer.Serialize(this, jsonOpts); - return System.Text.Encoding.UTF8.GetBytes(json); - } -} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Connection/WebSocketService.cs b/DysonNetwork.Sphere/Connection/WebSocketService.cs deleted file mode 100644 index c63575c..0000000 --- a/DysonNetwork.Sphere/Connection/WebSocketService.cs +++ /dev/null @@ -1,129 +0,0 @@ -using System.Collections.Concurrent; -using System.Net.WebSockets; - -namespace DysonNetwork.Sphere.Connection; - -public class WebSocketService -{ - private readonly IDictionary _handlerMap; - - public WebSocketService(IEnumerable handlers) - { - _handlerMap = handlers.ToDictionary(h => h.PacketType); - } - - private static readonly ConcurrentDictionary< - (Guid AccountId, string DeviceId), - (WebSocket Socket, CancellationTokenSource Cts) - > ActiveConnections = new(); - - private static readonly ConcurrentDictionary ActiveSubscriptions = new(); // deviceId -> chatRoomId - - public void SubscribeToChatRoom(string chatRoomId, string deviceId) - { - ActiveSubscriptions[deviceId] = chatRoomId; - } - - public void UnsubscribeFromChatRoom(string deviceId) - { - ActiveSubscriptions.TryRemove(deviceId, out _); - } - - public bool IsUserSubscribedToChatRoom(Guid accountId, string chatRoomId) - { - var userDeviceIds = ActiveConnections.Keys.Where(k => k.AccountId == accountId).Select(k => k.DeviceId); - foreach (var deviceId in userDeviceIds) - { - if (ActiveSubscriptions.TryGetValue(deviceId, out var subscribedChatRoomId) && subscribedChatRoomId == chatRoomId) - { - return true; - } - } - return false; - } - - public bool TryAdd( - (Guid AccountId, string DeviceId) key, - WebSocket socket, - CancellationTokenSource cts - ) - { - if (ActiveConnections.TryGetValue(key, out _)) - Disconnect(key, - "Just connected somewhere else with the same identifier."); // Disconnect the previous one using the same identifier - return ActiveConnections.TryAdd(key, (socket, cts)); - } - - public void Disconnect((Guid AccountId, string DeviceId) key, string? reason = null) - { - if (!ActiveConnections.TryGetValue(key, out var data)) return; - data.Socket.CloseAsync( - WebSocketCloseStatus.NormalClosure, - reason ?? "Server just decided to disconnect.", - CancellationToken.None - ); - data.Cts.Cancel(); - ActiveConnections.TryRemove(key, out _); - UnsubscribeFromChatRoom(key.DeviceId); - } - - public bool GetAccountIsConnected(Guid accountId) - { - return ActiveConnections.Any(c => c.Key.AccountId == accountId); - } - - public void SendPacketToAccount(Guid userId, WebSocketPacket packet) - { - var connections = ActiveConnections.Where(c => c.Key.AccountId == userId); - var packetBytes = packet.ToBytes(); - var segment = new ArraySegment(packetBytes); - - foreach (var connection in connections) - { - connection.Value.Socket.SendAsync( - segment, - WebSocketMessageType.Binary, - true, - CancellationToken.None - ); - } - } - - public void SendPacketToDevice(string deviceId, WebSocketPacket packet) - { - var connections = ActiveConnections.Where(c => c.Key.DeviceId == deviceId); - var packetBytes = packet.ToBytes(); - var segment = new ArraySegment(packetBytes); - - foreach (var connection in connections) - { - connection.Value.Socket.SendAsync( - segment, - WebSocketMessageType.Binary, - true, - CancellationToken.None - ); - } - } - - public async Task HandlePacket(Account.Account currentUser, string deviceId, WebSocketPacket packet, - WebSocket socket) - { - if (_handlerMap.TryGetValue(packet.Type, out var handler)) - { - await handler.HandleAsync(currentUser, deviceId, packet, socket, this); - return; - } - - await socket.SendAsync( - new ArraySegment(new WebSocketPacket - { - Type = WebSocketPacketType.Error, - ErrorMessage = $"Unprocessable packet: {packet.Type}" - }.ToBytes()), - WebSocketMessageType.Binary, - true, - CancellationToken.None - ); - } -} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Data/Migrations/20250628172328_AddOidcProviderSupport.Designer.cs b/DysonNetwork.Sphere/Data/Migrations/20250628172328_AddOidcProviderSupport.Designer.cs deleted file mode 100644 index 96facd5..0000000 --- a/DysonNetwork.Sphere/Data/Migrations/20250628172328_AddOidcProviderSupport.Designer.cs +++ /dev/null @@ -1,4000 +0,0 @@ -// -using System; -using System.Collections.Generic; -using System.Text.Json; -using DysonNetwork.Sphere; -using DysonNetwork.Sphere.Account; -using DysonNetwork.Sphere.Chat; -using DysonNetwork.Sphere.Connection.WebReader; -using DysonNetwork.Sphere.Storage; -using DysonNetwork.Sphere.Wallet; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using NetTopologySuite.Geometries; -using NodaTime; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; -using NpgsqlTypes; - -#nullable disable - -namespace DysonNetwork.Sphere.Data.Migrations -{ - [DbContext(typeof(AppDatabase))] - [Migration("20250628172328_AddOidcProviderSupport")] - partial class AddOidcProviderSupport - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "9.0.3") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "postgis"); - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.AbuseReport", 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.Sphere.Account.Account", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("ActivatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("activated_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("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("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.Sphere.Account.AccountAuthFactor", 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.Sphere.Account.AccountConnection", 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.Sphere.Account.AccountContact", 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("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.Sphere.Account.ActionLog", 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("geometry") - .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.Sphere.Account.Badge", 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.Sphere.Account.CheckInResult", 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("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.Sphere.Account.MagicSpell", 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.Sphere.Account.Notification", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("Content") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .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>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("Priority") - .HasColumnType("integer") - .HasColumnName("priority"); - - b.Property("Subtitle") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("subtitle"); - - b.Property("Title") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("title"); - - b.Property("Topic") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("topic"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("ViewedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("viewed_at"); - - b.HasKey("Id") - .HasName("pk_notifications"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_notifications_account_id"); - - b.ToTable("notifications", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.NotificationPushSubscription", 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(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("device_id"); - - b.Property("DeviceToken") - .IsRequired() - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("device_token"); - - b.Property("LastUsedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_used_at"); - - b.Property("Provider") - .HasColumnType("integer") - .HasColumnName("provider"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_notification_push_subscriptions"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_notification_push_subscriptions_account_id"); - - b.HasIndex("DeviceToken", "DeviceId", "AccountId") - .IsUnique() - .HasDatabaseName("ix_notification_push_subscriptions_device_token_device_id_acco"); - - b.ToTable("notification_push_subscriptions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Profile", 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("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_id"); - - 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("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("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - b.Property("Pronouns") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("pronouns"); - - b.Property("StellarMembership") - .HasColumnType("jsonb") - .HasColumnName("stellar_membership"); - - 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("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.Sphere.Account.Relationship", 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.Sphere.Account.Status", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - 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("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("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.Sphere.Auth.Challenge", 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") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("device_id"); - - 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("geometry") - .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("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_challenges"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_auth_challenges_account_id"); - - b.ToTable("auth_challenges", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Session", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("ChallengeId") - .HasColumnType("uuid") - .HasColumnName("challenge_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("Label") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("label"); - - b.Property("LastGrantedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_granted_at"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - 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.ToTable("auth_sessions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatMember", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("BreakUntil") - .HasColumnType("timestamp with time zone") - .HasColumnName("break_until"); - - b.Property("ChatRoomId") - .HasColumnType("uuid") - .HasColumnName("chat_room_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("IsBot") - .HasColumnType("boolean") - .HasColumnName("is_bot"); - - b.Property("JoinedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("joined_at"); - - b.Property("LastReadAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_read_at"); - - b.Property("LeaveAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("leave_at"); - - b.Property("Nick") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("nick"); - - b.Property("Notify") - .HasColumnType("integer") - .HasColumnName("notify"); - - b.Property("Role") - .HasColumnType("integer") - .HasColumnName("role"); - - b.Property("TimeoutCause") - .HasColumnType("jsonb") - .HasColumnName("timeout_cause"); - - b.Property("TimeoutUntil") - .HasColumnType("timestamp with time zone") - .HasColumnName("timeout_until"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_chat_members"); - - b.HasAlternateKey("ChatRoomId", "AccountId") - .HasName("ak_chat_members_chat_room_id_account_id"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_chat_members_account_id"); - - b.ToTable("chat_members", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatRoom", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("Background") - .HasColumnType("jsonb") - .HasColumnName("background"); - - b.Property("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_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("Description") - .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") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("Picture") - .HasColumnType("jsonb") - .HasColumnName("picture"); - - b.Property("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - b.Property("RealmId") - .HasColumnType("uuid") - .HasColumnName("realm_id"); - - b.Property("Type") - .HasColumnType("integer") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_chat_rooms"); - - b.HasIndex("RealmId") - .HasDatabaseName("ix_chat_rooms_realm_id"); - - b.ToTable("chat_rooms", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.Message", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property>("Attachments") - .IsRequired() - .HasColumnType("jsonb") - .HasColumnName("attachments"); - - b.Property("ChatRoomId") - .HasColumnType("uuid") - .HasColumnName("chat_room_id"); - - b.Property("Content") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .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("EditedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("edited_at"); - - b.Property("ForwardedMessageId") - .HasColumnType("uuid") - .HasColumnName("forwarded_message_id"); - - b.Property>("MembersMentioned") - .HasColumnType("jsonb") - .HasColumnName("members_mentioned"); - - b.Property>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("Nonce") - .IsRequired() - .HasMaxLength(36) - .HasColumnType("character varying(36)") - .HasColumnName("nonce"); - - b.Property("RepliedMessageId") - .HasColumnType("uuid") - .HasColumnName("replied_message_id"); - - b.Property("SenderId") - .HasColumnType("uuid") - .HasColumnName("sender_id"); - - 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_chat_messages"); - - b.HasIndex("ChatRoomId") - .HasDatabaseName("ix_chat_messages_chat_room_id"); - - b.HasIndex("ForwardedMessageId") - .HasDatabaseName("ix_chat_messages_forwarded_message_id"); - - b.HasIndex("RepliedMessageId") - .HasDatabaseName("ix_chat_messages_replied_message_id"); - - b.HasIndex("SenderId") - .HasDatabaseName("ix_chat_messages_sender_id"); - - b.ToTable("chat_messages", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.MessageReaction", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("Attitude") - .HasColumnType("integer") - .HasColumnName("attitude"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("MessageId") - .HasColumnType("uuid") - .HasColumnName("message_id"); - - b.Property("SenderId") - .HasColumnType("uuid") - .HasColumnName("sender_id"); - - b.Property("Symbol") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("symbol"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_chat_reactions"); - - b.HasIndex("MessageId") - .HasDatabaseName("ix_chat_reactions_message_id"); - - b.HasIndex("SenderId") - .HasDatabaseName("ix_chat_reactions_sender_id"); - - b.ToTable("chat_reactions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.RealtimeCall", 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("EndedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("ended_at"); - - b.Property("ProviderName") - .HasColumnType("text") - .HasColumnName("provider_name"); - - b.Property("RoomId") - .HasColumnType("uuid") - .HasColumnName("room_id"); - - b.Property("SenderId") - .HasColumnType("uuid") - .HasColumnName("sender_id"); - - b.Property("SessionId") - .HasColumnType("text") - .HasColumnName("session_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("UpstreamConfigJson") - .HasColumnType("jsonb") - .HasColumnName("upstream"); - - b.HasKey("Id") - .HasName("pk_chat_realtime_call"); - - b.HasIndex("RoomId") - .HasDatabaseName("ix_chat_realtime_call_room_id"); - - b.HasIndex("SenderId") - .HasDatabaseName("ix_chat_realtime_call_sender_id"); - - b.ToTable("chat_realtime_call", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Connection.WebReader.WebArticle", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("Author") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("author"); - - b.Property("Content") - .HasColumnType("text") - .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("FeedId") - .HasColumnType("uuid") - .HasColumnName("feed_id"); - - b.Property>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("Preview") - .HasColumnType("jsonb") - .HasColumnName("preview"); - - b.Property("PublishedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("published_at"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("title"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("Url") - .IsRequired() - .HasMaxLength(8192) - .HasColumnType("character varying(8192)") - .HasColumnName("url"); - - b.HasKey("Id") - .HasName("pk_web_articles"); - - b.HasIndex("FeedId") - .HasDatabaseName("ix_web_articles_feed_id"); - - b.HasIndex("Url") - .IsUnique() - .HasDatabaseName("ix_web_articles_url"); - - b.ToTable("web_articles", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Connection.WebReader.WebFeed", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("Config") - .IsRequired() - .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("Description") - .HasMaxLength(8192) - .HasColumnType("character varying(8192)") - .HasColumnName("description"); - - b.Property("Preview") - .HasColumnType("jsonb") - .HasColumnName("preview"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("title"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("Url") - .IsRequired() - .HasMaxLength(8192) - .HasColumnType("character varying(8192)") - .HasColumnName("url"); - - b.HasKey("Id") - .HasName("pk_web_feeds"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_web_feeds_publisher_id"); - - b.HasIndex("Url") - .IsUnique() - .HasDatabaseName("ix_web_feeds_url"); - - b.ToTable("web_feeds", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomApp", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AllowOfflineAccess") - .HasColumnType("boolean") - .HasColumnName("allow_offline_access"); - - b.Property("AllowedGrantTypes") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("allowed_grant_types"); - - b.Property("AllowedScopes") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("allowed_scopes"); - - b.Property("ClientUri") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("client_uri"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("LogoUri") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("logo_uri"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("PostLogoutRedirectUris") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("post_logout_redirect_uris"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("RedirectUris") - .IsRequired() - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("redirect_uris"); - - b.Property("RequirePkce") - .HasColumnType("boolean") - .HasColumnName("require_pkce"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("slug"); - - b.Property("Status") - .HasColumnType("integer") - .HasColumnName("status"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("VerifiedAs") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("verified_as"); - - b.Property("VerifiedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("verified_at"); - - b.HasKey("Id") - .HasName("pk_custom_apps"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_custom_apps_publisher_id"); - - b.ToTable("custom_apps", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomAppSecret", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AppId") - .HasColumnType("uuid") - .HasColumnName("app_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("Description") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("ExpiredAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expired_at"); - - b.Property("IsOidc") - .HasColumnType("boolean") - .HasColumnName("is_oidc"); - - b.Property("Secret") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("secret"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_custom_app_secrets"); - - b.HasIndex("AppId") - .HasDatabaseName("ix_custom_app_secrets_app_id"); - - b.HasIndex("Secret") - .IsUnique() - .HasDatabaseName("ix_custom_app_secrets_secret"); - - b.ToTable("custom_app_secrets", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroup", 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.Sphere.Permission.PermissionGroupMember", 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.Sphere.Permission.PermissionNode", 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("Area") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("area"); - - 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("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", "Area", "Actor") - .HasDatabaseName("ix_permission_nodes_key_area_actor"); - - b.ToTable("permission_nodes", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.Post", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property>("Attachments") - .IsRequired() - .HasColumnType("jsonb") - .HasColumnName("attachments"); - - b.Property("Content") - .HasColumnType("text") - .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("Description") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("Downvotes") - .HasColumnType("integer") - .HasColumnName("downvotes"); - - b.Property("EditedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("edited_at"); - - b.Property("ForwardedPostId") - .HasColumnType("uuid") - .HasColumnName("forwarded_post_id"); - - b.Property("Language") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("language"); - - b.Property>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("PublishedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("published_at"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("RepliedPostId") - .HasColumnType("uuid") - .HasColumnName("replied_post_id"); - - b.Property("SearchVector") - .IsRequired() - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("tsvector") - .HasColumnName("search_vector") - .HasAnnotation("Npgsql:TsVectorConfig", "simple") - .HasAnnotation("Npgsql:TsVectorProperties", new[] { "Title", "Description", "Content" }); - - b.Property>("SensitiveMarks") - .HasColumnType("jsonb") - .HasColumnName("sensitive_marks"); - - b.Property("Title") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("title"); - - b.Property("Type") - .HasColumnType("integer") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("Upvotes") - .HasColumnType("integer") - .HasColumnName("upvotes"); - - b.Property("ViewsTotal") - .HasColumnType("integer") - .HasColumnName("views_total"); - - b.Property("ViewsUnique") - .HasColumnType("integer") - .HasColumnName("views_unique"); - - b.Property("Visibility") - .HasColumnType("integer") - .HasColumnName("visibility"); - - b.HasKey("Id") - .HasName("pk_posts"); - - b.HasIndex("ForwardedPostId") - .HasDatabaseName("ix_posts_forwarded_post_id"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_posts_publisher_id"); - - b.HasIndex("RepliedPostId") - .HasDatabaseName("ix_posts_replied_post_id"); - - b.HasIndex("SearchVector") - .HasDatabaseName("ix_posts_search_vector"); - - NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex("SearchVector"), "GIN"); - - b.ToTable("posts", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostCategory", 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("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("name"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_categories"); - - b.ToTable("post_categories", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostCollection", 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("Description") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("name"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_collections"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_post_collections_publisher_id"); - - b.ToTable("post_collections", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostReaction", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("Attitude") - .HasColumnType("integer") - .HasColumnName("attitude"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("PostId") - .HasColumnType("uuid") - .HasColumnName("post_id"); - - b.Property("Symbol") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("symbol"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_reactions"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_post_reactions_account_id"); - - b.HasIndex("PostId") - .HasDatabaseName("ix_post_reactions_post_id"); - - b.ToTable("post_reactions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostTag", 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("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("name"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_tags"); - - b.ToTable("post_tags", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.Publisher", 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("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_id"); - - b.Property("Bio") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("bio"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - 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("Picture") - .HasColumnType("jsonb") - .HasColumnName("picture"); - - b.Property("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - b.Property("RealmId") - .HasColumnType("uuid") - .HasColumnName("realm_id"); - - b.Property("Type") - .HasColumnType("integer") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("Verification") - .HasColumnType("jsonb") - .HasColumnName("verification"); - - b.HasKey("Id") - .HasName("pk_publishers"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_publishers_account_id"); - - b.HasIndex("Name") - .IsUnique() - .HasDatabaseName("ix_publishers_name"); - - b.HasIndex("RealmId") - .HasDatabaseName("ix_publishers_realm_id"); - - b.ToTable("publishers", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherFeature", 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("ExpiredAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expired_at"); - - b.Property("Flag") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("flag"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_publisher_features"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_publisher_features_publisher_id"); - - b.ToTable("publisher_features", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherMember", b => - { - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_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("Role") - .HasColumnType("integer") - .HasColumnName("role"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("PublisherId", "AccountId") - .HasName("pk_publisher_members"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_publisher_members_account_id"); - - b.ToTable("publisher_members", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherSubscription", 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("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("Status") - .HasColumnType("integer") - .HasColumnName("status"); - - b.Property("Tier") - .HasColumnType("integer") - .HasColumnName("tier"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_publisher_subscriptions"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_publisher_subscriptions_account_id"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_publisher_subscriptions_publisher_id"); - - b.ToTable("publisher_subscriptions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.Realm", 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("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_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("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("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - 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("AccountId") - .HasDatabaseName("ix_realms_account_id"); - - b.HasIndex("Slug") - .IsUnique() - .HasDatabaseName("ix_realms_slug"); - - b.ToTable("realms", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.RealmMember", 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.HasIndex("AccountId") - .HasDatabaseName("ix_realm_members_account_id"); - - b.ToTable("realm_members", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.RealmTag", b => - { - b.Property("RealmId") - .HasColumnType("uuid") - .HasColumnName("realm_id"); - - b.Property("TagId") - .HasColumnType("uuid") - .HasColumnName("tag_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("RealmId", "TagId") - .HasName("pk_realm_tags"); - - b.HasIndex("TagId") - .HasDatabaseName("ix_realm_tags_tag_id"); - - b.ToTable("realm_tags", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.Tag", 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("Name") - .IsRequired() - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("name"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_tags"); - - b.ToTable("tags", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.Sticker", 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("Image") - .HasColumnType("jsonb") - .HasColumnName("image"); - - b.Property("ImageId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("image_id"); - - b.Property("PackId") - .HasColumnType("uuid") - .HasColumnName("pack_id"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_stickers"); - - b.HasIndex("PackId") - .HasDatabaseName("ix_stickers_pack_id"); - - b.HasIndex("Slug") - .HasDatabaseName("ix_stickers_slug"); - - b.ToTable("stickers", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.StickerPack", 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("Description") - .IsRequired() - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("Prefix") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("prefix"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_sticker_packs"); - - b.HasIndex("Prefix") - .IsUnique() - .HasDatabaseName("ix_sticker_packs_prefix"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_sticker_packs_publisher_id"); - - b.ToTable("sticker_packs", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFile", b => - { - b.Property("Id") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .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("Description") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property>("FileMeta") - .HasColumnType("jsonb") - .HasColumnName("file_meta"); - - b.Property("HasCompression") - .HasColumnType("boolean") - .HasColumnName("has_compression"); - - b.Property("Hash") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("hash"); - - b.Property("IsMarkedRecycle") - .HasColumnType("boolean") - .HasColumnName("is_marked_recycle"); - - b.Property("MessageId") - .HasColumnType("uuid") - .HasColumnName("message_id"); - - b.Property("MimeType") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("mime_type"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("PostId") - .HasColumnType("uuid") - .HasColumnName("post_id"); - - b.Property>("SensitiveMarks") - .HasColumnType("jsonb") - .HasColumnName("sensitive_marks"); - - b.Property("Size") - .HasColumnType("bigint") - .HasColumnName("size"); - - b.Property("StorageId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("storage_id"); - - b.Property("StorageUrl") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("storage_url"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("UploadedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("uploaded_at"); - - b.Property("UploadedTo") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("uploaded_to"); - - b.Property>("UserMeta") - .HasColumnType("jsonb") - .HasColumnName("user_meta"); - - b.HasKey("Id") - .HasName("pk_files"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_files_account_id"); - - b.HasIndex("MessageId") - .HasDatabaseName("ix_files_message_id"); - - b.HasIndex("PostId") - .HasDatabaseName("ix_files_post_id"); - - b.ToTable("files", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFileReference", 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("ExpiredAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expired_at"); - - b.Property("FileId") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("file_id"); - - b.Property("ResourceId") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("resource_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("Usage") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("usage"); - - b.HasKey("Id") - .HasName("pk_file_references"); - - b.HasIndex("FileId") - .HasDatabaseName("ix_file_references_file_id"); - - b.ToTable("file_references", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Coupon", 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.Sphere.Wallet.Order", 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("IssuerAppId") - .HasColumnType("uuid") - .HasColumnName("issuer_app_id"); - - b.Property>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("PayeeWalletId") - .HasColumnType("uuid") - .HasColumnName("payee_wallet_id"); - - 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("IssuerAppId") - .HasDatabaseName("ix_payment_orders_issuer_app_id"); - - 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.Sphere.Wallet.Subscription", 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.ToTable("wallet_subscriptions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Transaction", 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.Sphere.Wallet.Wallet", 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.Sphere.Wallet.WalletPocket", 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("PostPostCategory", b => - { - b.Property("CategoriesId") - .HasColumnType("uuid") - .HasColumnName("categories_id"); - - b.Property("PostsId") - .HasColumnType("uuid") - .HasColumnName("posts_id"); - - b.HasKey("CategoriesId", "PostsId") - .HasName("pk_post_category_links"); - - b.HasIndex("PostsId") - .HasDatabaseName("ix_post_category_links_posts_id"); - - b.ToTable("post_category_links", (string)null); - }); - - modelBuilder.Entity("PostPostCollection", b => - { - b.Property("CollectionsId") - .HasColumnType("uuid") - .HasColumnName("collections_id"); - - b.Property("PostsId") - .HasColumnType("uuid") - .HasColumnName("posts_id"); - - b.HasKey("CollectionsId", "PostsId") - .HasName("pk_post_collection_links"); - - b.HasIndex("PostsId") - .HasDatabaseName("ix_post_collection_links_posts_id"); - - b.ToTable("post_collection_links", (string)null); - }); - - modelBuilder.Entity("PostPostTag", b => - { - b.Property("PostsId") - .HasColumnType("uuid") - .HasColumnName("posts_id"); - - b.Property("TagsId") - .HasColumnType("uuid") - .HasColumnName("tags_id"); - - b.HasKey("PostsId", "TagsId") - .HasName("pk_post_tag_links"); - - b.HasIndex("TagsId") - .HasDatabaseName("ix_post_tag_links_tags_id"); - - b.ToTable("post_tag_links", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.AbuseReport", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_abuse_reports_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.AccountAuthFactor", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("AuthFactors") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_auth_factors_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.AccountConnection", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Connections") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_connections_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.AccountContact", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Contacts") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_contacts_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.ActionLog", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_action_logs_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Badge", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Badges") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_badges_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.CheckInResult", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_check_in_results_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.MagicSpell", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .HasConstraintName("fk_magic_spells_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Notification", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_notifications_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.NotificationPushSubscription", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_notification_push_subscriptions_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Profile", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithOne("Profile") - .HasForeignKey("DysonNetwork.Sphere.Account.Profile", "AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_profiles_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Relationship", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("OutgoingRelationships") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_relationships_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Account.Account", "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.Sphere.Account.Status", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_statuses_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Challenge", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Challenges") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_challenges_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Session", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Sessions") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_sessions_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Auth.Challenge", "Challenge") - .WithMany() - .HasForeignKey("ChallengeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_sessions_auth_challenges_challenge_id"); - - b.Navigation("Account"); - - b.Navigation("Challenge"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatMember", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_members_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatRoom", "ChatRoom") - .WithMany("Members") - .HasForeignKey("ChatRoomId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_members_chat_rooms_chat_room_id"); - - b.Navigation("Account"); - - b.Navigation("ChatRoom"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatRoom", b => - { - b.HasOne("DysonNetwork.Sphere.Realm.Realm", "Realm") - .WithMany("ChatRooms") - .HasForeignKey("RealmId") - .HasConstraintName("fk_chat_rooms_realms_realm_id"); - - b.Navigation("Realm"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.Message", b => - { - b.HasOne("DysonNetwork.Sphere.Chat.ChatRoom", "ChatRoom") - .WithMany() - .HasForeignKey("ChatRoomId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_messages_chat_rooms_chat_room_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.Message", "ForwardedMessage") - .WithMany() - .HasForeignKey("ForwardedMessageId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_chat_messages_chat_messages_forwarded_message_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.Message", "RepliedMessage") - .WithMany() - .HasForeignKey("RepliedMessageId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_chat_messages_chat_messages_replied_message_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatMember", "Sender") - .WithMany() - .HasForeignKey("SenderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_messages_chat_members_sender_id"); - - b.Navigation("ChatRoom"); - - b.Navigation("ForwardedMessage"); - - b.Navigation("RepliedMessage"); - - b.Navigation("Sender"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.MessageReaction", b => - { - b.HasOne("DysonNetwork.Sphere.Chat.Message", "Message") - .WithMany("Reactions") - .HasForeignKey("MessageId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_reactions_chat_messages_message_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatMember", "Sender") - .WithMany() - .HasForeignKey("SenderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_reactions_chat_members_sender_id"); - - b.Navigation("Message"); - - b.Navigation("Sender"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.RealtimeCall", b => - { - b.HasOne("DysonNetwork.Sphere.Chat.ChatRoom", "Room") - .WithMany() - .HasForeignKey("RoomId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_realtime_call_chat_rooms_room_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatMember", "Sender") - .WithMany() - .HasForeignKey("SenderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_realtime_call_chat_members_sender_id"); - - b.Navigation("Room"); - - b.Navigation("Sender"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Connection.WebReader.WebArticle", b => - { - b.HasOne("DysonNetwork.Sphere.Connection.WebReader.WebFeed", "Feed") - .WithMany("Articles") - .HasForeignKey("FeedId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_web_articles_web_feeds_feed_id"); - - b.Navigation("Feed"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Connection.WebReader.WebFeed", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany() - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_web_feeds_publishers_publisher_id"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomApp", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Developer") - .WithMany() - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_custom_apps_publishers_publisher_id"); - - b.Navigation("Developer"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomAppSecret", b => - { - b.HasOne("DysonNetwork.Sphere.Developer.CustomApp", "App") - .WithMany("Secrets") - .HasForeignKey("AppId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_custom_app_secrets_custom_apps_app_id"); - - b.Navigation("App"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroupMember", b => - { - b.HasOne("DysonNetwork.Sphere.Permission.PermissionGroup", "Group") - .WithMany("Members") - .HasForeignKey("GroupId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_permission_group_members_permission_groups_group_id"); - - b.Navigation("Group"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionNode", b => - { - b.HasOne("DysonNetwork.Sphere.Permission.PermissionGroup", "Group") - .WithMany("Nodes") - .HasForeignKey("GroupId") - .HasConstraintName("fk_permission_nodes_permission_groups_group_id"); - - b.Navigation("Group"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.Post", b => - { - b.HasOne("DysonNetwork.Sphere.Post.Post", "ForwardedPost") - .WithMany() - .HasForeignKey("ForwardedPostId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_posts_posts_forwarded_post_id"); - - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Posts") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_posts_publishers_publisher_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", "RepliedPost") - .WithMany() - .HasForeignKey("RepliedPostId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_posts_posts_replied_post_id"); - - b.Navigation("ForwardedPost"); - - b.Navigation("Publisher"); - - b.Navigation("RepliedPost"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostCollection", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Collections") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_collections_publishers_publisher_id"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostReaction", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_reactions_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", "Post") - .WithMany("Reactions") - .HasForeignKey("PostId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_reactions_posts_post_id"); - - b.Navigation("Account"); - - b.Navigation("Post"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.Publisher", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .HasConstraintName("fk_publishers_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Realm.Realm", "Realm") - .WithMany() - .HasForeignKey("RealmId") - .HasConstraintName("fk_publishers_realms_realm_id"); - - b.Navigation("Account"); - - b.Navigation("Realm"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherFeature", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany() - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_features_publishers_publisher_id"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherMember", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_members_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Members") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_members_publishers_publisher_id"); - - b.Navigation("Account"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherSubscription", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_subscriptions_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Subscriptions") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_subscriptions_publishers_publisher_id"); - - b.Navigation("Account"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.Realm", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_realms_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.RealmMember", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_realm_members_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Realm.Realm", "Realm") - .WithMany("Members") - .HasForeignKey("RealmId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_realm_members_realms_realm_id"); - - b.Navigation("Account"); - - b.Navigation("Realm"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.RealmTag", b => - { - b.HasOne("DysonNetwork.Sphere.Realm.Realm", "Realm") - .WithMany("RealmTags") - .HasForeignKey("RealmId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_realm_tags_realms_realm_id"); - - b.HasOne("DysonNetwork.Sphere.Realm.Tag", "Tag") - .WithMany("RealmTags") - .HasForeignKey("TagId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_realm_tags_tags_tag_id"); - - b.Navigation("Realm"); - - b.Navigation("Tag"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.Sticker", b => - { - b.HasOne("DysonNetwork.Sphere.Sticker.StickerPack", "Pack") - .WithMany() - .HasForeignKey("PackId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_stickers_sticker_packs_pack_id"); - - b.Navigation("Pack"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.StickerPack", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany() - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_sticker_packs_publishers_publisher_id"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFile", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_files_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.Message", null) - .WithMany("OutdatedAttachments") - .HasForeignKey("MessageId") - .HasConstraintName("fk_files_chat_messages_message_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany("OutdatedAttachments") - .HasForeignKey("PostId") - .HasConstraintName("fk_files_posts_post_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFileReference", b => - { - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "File") - .WithMany() - .HasForeignKey("FileId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_file_references_files_file_id"); - - b.Navigation("File"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Order", b => - { - b.HasOne("DysonNetwork.Sphere.Developer.CustomApp", "IssuerApp") - .WithMany() - .HasForeignKey("IssuerAppId") - .HasConstraintName("fk_payment_orders_custom_apps_issuer_app_id"); - - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "PayeeWallet") - .WithMany() - .HasForeignKey("PayeeWalletId") - .HasConstraintName("fk_payment_orders_wallets_payee_wallet_id"); - - b.HasOne("DysonNetwork.Sphere.Wallet.Transaction", "Transaction") - .WithMany() - .HasForeignKey("TransactionId") - .HasConstraintName("fk_payment_orders_payment_transactions_transaction_id"); - - b.Navigation("IssuerApp"); - - b.Navigation("PayeeWallet"); - - b.Navigation("Transaction"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Subscription", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Subscriptions") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_wallet_subscriptions_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Wallet.Coupon", "Coupon") - .WithMany() - .HasForeignKey("CouponId") - .HasConstraintName("fk_wallet_subscriptions_wallet_coupons_coupon_id"); - - b.Navigation("Account"); - - b.Navigation("Coupon"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Transaction", b => - { - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "PayeeWallet") - .WithMany() - .HasForeignKey("PayeeWalletId") - .HasConstraintName("fk_payment_transactions_wallets_payee_wallet_id"); - - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "PayerWallet") - .WithMany() - .HasForeignKey("PayerWalletId") - .HasConstraintName("fk_payment_transactions_wallets_payer_wallet_id"); - - b.Navigation("PayeeWallet"); - - b.Navigation("PayerWallet"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Wallet", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_wallets_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.WalletPocket", b => - { - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "Wallet") - .WithMany("Pockets") - .HasForeignKey("WalletId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_wallet_pockets_wallets_wallet_id"); - - b.Navigation("Wallet"); - }); - - modelBuilder.Entity("PostPostCategory", b => - { - b.HasOne("DysonNetwork.Sphere.Post.PostCategory", null) - .WithMany() - .HasForeignKey("CategoriesId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_category_links_post_categories_categories_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany() - .HasForeignKey("PostsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_category_links_posts_posts_id"); - }); - - modelBuilder.Entity("PostPostCollection", b => - { - b.HasOne("DysonNetwork.Sphere.Post.PostCollection", null) - .WithMany() - .HasForeignKey("CollectionsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_collection_links_post_collections_collections_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany() - .HasForeignKey("PostsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_collection_links_posts_posts_id"); - }); - - modelBuilder.Entity("PostPostTag", b => - { - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany() - .HasForeignKey("PostsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_tag_links_posts_posts_id"); - - b.HasOne("DysonNetwork.Sphere.Post.PostTag", null) - .WithMany() - .HasForeignKey("TagsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_tag_links_post_tags_tags_id"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Account", 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"); - - b.Navigation("Subscriptions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatRoom", b => - { - b.Navigation("Members"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.Message", b => - { - b.Navigation("OutdatedAttachments"); - - b.Navigation("Reactions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Connection.WebReader.WebFeed", b => - { - b.Navigation("Articles"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomApp", b => - { - b.Navigation("Secrets"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroup", b => - { - b.Navigation("Members"); - - b.Navigation("Nodes"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.Post", b => - { - b.Navigation("OutdatedAttachments"); - - b.Navigation("Reactions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.Publisher", b => - { - b.Navigation("Collections"); - - b.Navigation("Members"); - - b.Navigation("Posts"); - - b.Navigation("Subscriptions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.Realm", b => - { - b.Navigation("ChatRooms"); - - b.Navigation("Members"); - - b.Navigation("RealmTags"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.Tag", b => - { - b.Navigation("RealmTags"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Wallet", b => - { - b.Navigation("Pockets"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/DysonNetwork.Sphere/Data/Migrations/20250628172328_AddOidcProviderSupport.cs b/DysonNetwork.Sphere/Data/Migrations/20250628172328_AddOidcProviderSupport.cs deleted file mode 100644 index 2d3ae2a..0000000 --- a/DysonNetwork.Sphere/Data/Migrations/20250628172328_AddOidcProviderSupport.cs +++ /dev/null @@ -1,139 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace DysonNetwork.Sphere.Data.Migrations -{ - /// - public partial class AddOidcProviderSupport : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.RenameColumn( - name: "remarks", - table: "custom_app_secrets", - newName: "description"); - - migrationBuilder.AddColumn( - name: "allow_offline_access", - table: "custom_apps", - type: "boolean", - nullable: false, - defaultValue: false); - - migrationBuilder.AddColumn( - name: "allowed_grant_types", - table: "custom_apps", - type: "character varying(256)", - maxLength: 256, - nullable: false, - defaultValue: ""); - - migrationBuilder.AddColumn( - name: "allowed_scopes", - table: "custom_apps", - type: "character varying(256)", - maxLength: 256, - nullable: true); - - migrationBuilder.AddColumn( - name: "client_uri", - table: "custom_apps", - type: "character varying(1024)", - maxLength: 1024, - nullable: true); - - migrationBuilder.AddColumn( - name: "logo_uri", - table: "custom_apps", - type: "character varying(4096)", - maxLength: 4096, - nullable: true); - - migrationBuilder.AddColumn( - name: "post_logout_redirect_uris", - table: "custom_apps", - type: "character varying(4096)", - maxLength: 4096, - nullable: true); - - migrationBuilder.AddColumn( - name: "redirect_uris", - table: "custom_apps", - type: "character varying(4096)", - maxLength: 4096, - nullable: false, - defaultValue: ""); - - migrationBuilder.AddColumn( - name: "require_pkce", - table: "custom_apps", - type: "boolean", - nullable: false, - defaultValue: false); - - migrationBuilder.AddColumn( - name: "is_oidc", - table: "custom_app_secrets", - type: "boolean", - nullable: false, - defaultValue: false); - - migrationBuilder.CreateIndex( - name: "ix_custom_app_secrets_secret", - table: "custom_app_secrets", - column: "secret", - unique: true); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropIndex( - name: "ix_custom_app_secrets_secret", - table: "custom_app_secrets"); - - migrationBuilder.DropColumn( - name: "allow_offline_access", - table: "custom_apps"); - - migrationBuilder.DropColumn( - name: "allowed_grant_types", - table: "custom_apps"); - - migrationBuilder.DropColumn( - name: "allowed_scopes", - table: "custom_apps"); - - migrationBuilder.DropColumn( - name: "client_uri", - table: "custom_apps"); - - migrationBuilder.DropColumn( - name: "logo_uri", - table: "custom_apps"); - - migrationBuilder.DropColumn( - name: "post_logout_redirect_uris", - table: "custom_apps"); - - migrationBuilder.DropColumn( - name: "redirect_uris", - table: "custom_apps"); - - migrationBuilder.DropColumn( - name: "require_pkce", - table: "custom_apps"); - - migrationBuilder.DropColumn( - name: "is_oidc", - table: "custom_app_secrets"); - - migrationBuilder.RenameColumn( - name: "description", - table: "custom_app_secrets", - newName: "remarks"); - } - } -} diff --git a/DysonNetwork.Sphere/Data/Migrations/20250629084150_AuthSessionWithApp.Designer.cs b/DysonNetwork.Sphere/Data/Migrations/20250629084150_AuthSessionWithApp.Designer.cs deleted file mode 100644 index 2f59182..0000000 --- a/DysonNetwork.Sphere/Data/Migrations/20250629084150_AuthSessionWithApp.Designer.cs +++ /dev/null @@ -1,4014 +0,0 @@ -// -using System; -using System.Collections.Generic; -using System.Text.Json; -using DysonNetwork.Sphere; -using DysonNetwork.Sphere.Account; -using DysonNetwork.Sphere.Chat; -using DysonNetwork.Sphere.Connection.WebReader; -using DysonNetwork.Sphere.Storage; -using DysonNetwork.Sphere.Wallet; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using NetTopologySuite.Geometries; -using NodaTime; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; -using NpgsqlTypes; - -#nullable disable - -namespace DysonNetwork.Sphere.Data.Migrations -{ - [DbContext(typeof(AppDatabase))] - [Migration("20250629084150_AuthSessionWithApp")] - partial class AuthSessionWithApp - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "9.0.3") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "postgis"); - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.AbuseReport", 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.Sphere.Account.Account", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("ActivatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("activated_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("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("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.Sphere.Account.AccountAuthFactor", 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.Sphere.Account.AccountConnection", 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.Sphere.Account.AccountContact", 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("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.Sphere.Account.ActionLog", 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("geometry") - .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.Sphere.Account.Badge", 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.Sphere.Account.CheckInResult", 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("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.Sphere.Account.MagicSpell", 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.Sphere.Account.Notification", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("Content") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .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>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("Priority") - .HasColumnType("integer") - .HasColumnName("priority"); - - b.Property("Subtitle") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("subtitle"); - - b.Property("Title") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("title"); - - b.Property("Topic") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("topic"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("ViewedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("viewed_at"); - - b.HasKey("Id") - .HasName("pk_notifications"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_notifications_account_id"); - - b.ToTable("notifications", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.NotificationPushSubscription", 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(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("device_id"); - - b.Property("DeviceToken") - .IsRequired() - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("device_token"); - - b.Property("LastUsedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_used_at"); - - b.Property("Provider") - .HasColumnType("integer") - .HasColumnName("provider"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_notification_push_subscriptions"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_notification_push_subscriptions_account_id"); - - b.HasIndex("DeviceToken", "DeviceId", "AccountId") - .IsUnique() - .HasDatabaseName("ix_notification_push_subscriptions_device_token_device_id_acco"); - - b.ToTable("notification_push_subscriptions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Profile", 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("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_id"); - - 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("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("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - b.Property("Pronouns") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("pronouns"); - - b.Property("StellarMembership") - .HasColumnType("jsonb") - .HasColumnName("stellar_membership"); - - 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("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.Sphere.Account.Relationship", 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.Sphere.Account.Status", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - 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("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("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.Sphere.Auth.Challenge", 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") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("device_id"); - - 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("geometry") - .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("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_challenges"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_auth_challenges_account_id"); - - b.ToTable("auth_challenges", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Session", 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("ChallengeId") - .HasColumnType("uuid") - .HasColumnName("challenge_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("Label") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("label"); - - b.Property("LastGrantedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_granted_at"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_auth_sessions"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_auth_sessions_account_id"); - - b.HasIndex("AppId") - .HasDatabaseName("ix_auth_sessions_app_id"); - - b.HasIndex("ChallengeId") - .HasDatabaseName("ix_auth_sessions_challenge_id"); - - b.ToTable("auth_sessions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatMember", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("BreakUntil") - .HasColumnType("timestamp with time zone") - .HasColumnName("break_until"); - - b.Property("ChatRoomId") - .HasColumnType("uuid") - .HasColumnName("chat_room_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("IsBot") - .HasColumnType("boolean") - .HasColumnName("is_bot"); - - b.Property("JoinedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("joined_at"); - - b.Property("LastReadAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_read_at"); - - b.Property("LeaveAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("leave_at"); - - b.Property("Nick") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("nick"); - - b.Property("Notify") - .HasColumnType("integer") - .HasColumnName("notify"); - - b.Property("Role") - .HasColumnType("integer") - .HasColumnName("role"); - - b.Property("TimeoutCause") - .HasColumnType("jsonb") - .HasColumnName("timeout_cause"); - - b.Property("TimeoutUntil") - .HasColumnType("timestamp with time zone") - .HasColumnName("timeout_until"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_chat_members"); - - b.HasAlternateKey("ChatRoomId", "AccountId") - .HasName("ak_chat_members_chat_room_id_account_id"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_chat_members_account_id"); - - b.ToTable("chat_members", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatRoom", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("Background") - .HasColumnType("jsonb") - .HasColumnName("background"); - - b.Property("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_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("Description") - .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") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("Picture") - .HasColumnType("jsonb") - .HasColumnName("picture"); - - b.Property("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - b.Property("RealmId") - .HasColumnType("uuid") - .HasColumnName("realm_id"); - - b.Property("Type") - .HasColumnType("integer") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_chat_rooms"); - - b.HasIndex("RealmId") - .HasDatabaseName("ix_chat_rooms_realm_id"); - - b.ToTable("chat_rooms", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.Message", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property>("Attachments") - .IsRequired() - .HasColumnType("jsonb") - .HasColumnName("attachments"); - - b.Property("ChatRoomId") - .HasColumnType("uuid") - .HasColumnName("chat_room_id"); - - b.Property("Content") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .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("EditedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("edited_at"); - - b.Property("ForwardedMessageId") - .HasColumnType("uuid") - .HasColumnName("forwarded_message_id"); - - b.Property>("MembersMentioned") - .HasColumnType("jsonb") - .HasColumnName("members_mentioned"); - - b.Property>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("Nonce") - .IsRequired() - .HasMaxLength(36) - .HasColumnType("character varying(36)") - .HasColumnName("nonce"); - - b.Property("RepliedMessageId") - .HasColumnType("uuid") - .HasColumnName("replied_message_id"); - - b.Property("SenderId") - .HasColumnType("uuid") - .HasColumnName("sender_id"); - - 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_chat_messages"); - - b.HasIndex("ChatRoomId") - .HasDatabaseName("ix_chat_messages_chat_room_id"); - - b.HasIndex("ForwardedMessageId") - .HasDatabaseName("ix_chat_messages_forwarded_message_id"); - - b.HasIndex("RepliedMessageId") - .HasDatabaseName("ix_chat_messages_replied_message_id"); - - b.HasIndex("SenderId") - .HasDatabaseName("ix_chat_messages_sender_id"); - - b.ToTable("chat_messages", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.MessageReaction", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("Attitude") - .HasColumnType("integer") - .HasColumnName("attitude"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("MessageId") - .HasColumnType("uuid") - .HasColumnName("message_id"); - - b.Property("SenderId") - .HasColumnType("uuid") - .HasColumnName("sender_id"); - - b.Property("Symbol") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("symbol"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_chat_reactions"); - - b.HasIndex("MessageId") - .HasDatabaseName("ix_chat_reactions_message_id"); - - b.HasIndex("SenderId") - .HasDatabaseName("ix_chat_reactions_sender_id"); - - b.ToTable("chat_reactions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.RealtimeCall", 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("EndedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("ended_at"); - - b.Property("ProviderName") - .HasColumnType("text") - .HasColumnName("provider_name"); - - b.Property("RoomId") - .HasColumnType("uuid") - .HasColumnName("room_id"); - - b.Property("SenderId") - .HasColumnType("uuid") - .HasColumnName("sender_id"); - - b.Property("SessionId") - .HasColumnType("text") - .HasColumnName("session_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("UpstreamConfigJson") - .HasColumnType("jsonb") - .HasColumnName("upstream"); - - b.HasKey("Id") - .HasName("pk_chat_realtime_call"); - - b.HasIndex("RoomId") - .HasDatabaseName("ix_chat_realtime_call_room_id"); - - b.HasIndex("SenderId") - .HasDatabaseName("ix_chat_realtime_call_sender_id"); - - b.ToTable("chat_realtime_call", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Connection.WebReader.WebArticle", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("Author") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("author"); - - b.Property("Content") - .HasColumnType("text") - .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("FeedId") - .HasColumnType("uuid") - .HasColumnName("feed_id"); - - b.Property>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("Preview") - .HasColumnType("jsonb") - .HasColumnName("preview"); - - b.Property("PublishedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("published_at"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("title"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("Url") - .IsRequired() - .HasMaxLength(8192) - .HasColumnType("character varying(8192)") - .HasColumnName("url"); - - b.HasKey("Id") - .HasName("pk_web_articles"); - - b.HasIndex("FeedId") - .HasDatabaseName("ix_web_articles_feed_id"); - - b.HasIndex("Url") - .IsUnique() - .HasDatabaseName("ix_web_articles_url"); - - b.ToTable("web_articles", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Connection.WebReader.WebFeed", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("Config") - .IsRequired() - .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("Description") - .HasMaxLength(8192) - .HasColumnType("character varying(8192)") - .HasColumnName("description"); - - b.Property("Preview") - .HasColumnType("jsonb") - .HasColumnName("preview"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("title"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("Url") - .IsRequired() - .HasMaxLength(8192) - .HasColumnType("character varying(8192)") - .HasColumnName("url"); - - b.HasKey("Id") - .HasName("pk_web_feeds"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_web_feeds_publisher_id"); - - b.HasIndex("Url") - .IsUnique() - .HasDatabaseName("ix_web_feeds_url"); - - b.ToTable("web_feeds", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomApp", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AllowOfflineAccess") - .HasColumnType("boolean") - .HasColumnName("allow_offline_access"); - - b.Property("AllowedGrantTypes") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("allowed_grant_types"); - - b.Property("AllowedScopes") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("allowed_scopes"); - - b.Property("ClientUri") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("client_uri"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("LogoUri") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("logo_uri"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("PostLogoutRedirectUris") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("post_logout_redirect_uris"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("RedirectUris") - .IsRequired() - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("redirect_uris"); - - b.Property("RequirePkce") - .HasColumnType("boolean") - .HasColumnName("require_pkce"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("slug"); - - b.Property("Status") - .HasColumnType("integer") - .HasColumnName("status"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("VerifiedAs") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("verified_as"); - - b.Property("VerifiedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("verified_at"); - - b.HasKey("Id") - .HasName("pk_custom_apps"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_custom_apps_publisher_id"); - - b.ToTable("custom_apps", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomAppSecret", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AppId") - .HasColumnType("uuid") - .HasColumnName("app_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("Description") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("ExpiredAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expired_at"); - - b.Property("IsOidc") - .HasColumnType("boolean") - .HasColumnName("is_oidc"); - - b.Property("Secret") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("secret"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_custom_app_secrets"); - - b.HasIndex("AppId") - .HasDatabaseName("ix_custom_app_secrets_app_id"); - - b.HasIndex("Secret") - .IsUnique() - .HasDatabaseName("ix_custom_app_secrets_secret"); - - b.ToTable("custom_app_secrets", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroup", 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.Sphere.Permission.PermissionGroupMember", 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.Sphere.Permission.PermissionNode", 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("Area") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("area"); - - 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("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", "Area", "Actor") - .HasDatabaseName("ix_permission_nodes_key_area_actor"); - - b.ToTable("permission_nodes", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.Post", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property>("Attachments") - .IsRequired() - .HasColumnType("jsonb") - .HasColumnName("attachments"); - - b.Property("Content") - .HasColumnType("text") - .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("Description") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("Downvotes") - .HasColumnType("integer") - .HasColumnName("downvotes"); - - b.Property("EditedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("edited_at"); - - b.Property("ForwardedPostId") - .HasColumnType("uuid") - .HasColumnName("forwarded_post_id"); - - b.Property("Language") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("language"); - - b.Property>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("PublishedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("published_at"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("RepliedPostId") - .HasColumnType("uuid") - .HasColumnName("replied_post_id"); - - b.Property("SearchVector") - .IsRequired() - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("tsvector") - .HasColumnName("search_vector") - .HasAnnotation("Npgsql:TsVectorConfig", "simple") - .HasAnnotation("Npgsql:TsVectorProperties", new[] { "Title", "Description", "Content" }); - - b.Property>("SensitiveMarks") - .HasColumnType("jsonb") - .HasColumnName("sensitive_marks"); - - b.Property("Title") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("title"); - - b.Property("Type") - .HasColumnType("integer") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("Upvotes") - .HasColumnType("integer") - .HasColumnName("upvotes"); - - b.Property("ViewsTotal") - .HasColumnType("integer") - .HasColumnName("views_total"); - - b.Property("ViewsUnique") - .HasColumnType("integer") - .HasColumnName("views_unique"); - - b.Property("Visibility") - .HasColumnType("integer") - .HasColumnName("visibility"); - - b.HasKey("Id") - .HasName("pk_posts"); - - b.HasIndex("ForwardedPostId") - .HasDatabaseName("ix_posts_forwarded_post_id"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_posts_publisher_id"); - - b.HasIndex("RepliedPostId") - .HasDatabaseName("ix_posts_replied_post_id"); - - b.HasIndex("SearchVector") - .HasDatabaseName("ix_posts_search_vector"); - - NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex("SearchVector"), "GIN"); - - b.ToTable("posts", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostCategory", 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("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("name"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_categories"); - - b.ToTable("post_categories", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostCollection", 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("Description") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("name"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_collections"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_post_collections_publisher_id"); - - b.ToTable("post_collections", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostReaction", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("Attitude") - .HasColumnType("integer") - .HasColumnName("attitude"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("PostId") - .HasColumnType("uuid") - .HasColumnName("post_id"); - - b.Property("Symbol") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("symbol"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_reactions"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_post_reactions_account_id"); - - b.HasIndex("PostId") - .HasDatabaseName("ix_post_reactions_post_id"); - - b.ToTable("post_reactions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostTag", 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("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("name"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_tags"); - - b.ToTable("post_tags", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.Publisher", 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("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_id"); - - b.Property("Bio") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("bio"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - 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("Picture") - .HasColumnType("jsonb") - .HasColumnName("picture"); - - b.Property("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - b.Property("RealmId") - .HasColumnType("uuid") - .HasColumnName("realm_id"); - - b.Property("Type") - .HasColumnType("integer") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("Verification") - .HasColumnType("jsonb") - .HasColumnName("verification"); - - b.HasKey("Id") - .HasName("pk_publishers"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_publishers_account_id"); - - b.HasIndex("Name") - .IsUnique() - .HasDatabaseName("ix_publishers_name"); - - b.HasIndex("RealmId") - .HasDatabaseName("ix_publishers_realm_id"); - - b.ToTable("publishers", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherFeature", 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("ExpiredAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expired_at"); - - b.Property("Flag") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("flag"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_publisher_features"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_publisher_features_publisher_id"); - - b.ToTable("publisher_features", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherMember", b => - { - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_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("Role") - .HasColumnType("integer") - .HasColumnName("role"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("PublisherId", "AccountId") - .HasName("pk_publisher_members"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_publisher_members_account_id"); - - b.ToTable("publisher_members", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherSubscription", 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("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("Status") - .HasColumnType("integer") - .HasColumnName("status"); - - b.Property("Tier") - .HasColumnType("integer") - .HasColumnName("tier"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_publisher_subscriptions"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_publisher_subscriptions_account_id"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_publisher_subscriptions_publisher_id"); - - b.ToTable("publisher_subscriptions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.Realm", 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("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_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("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("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - 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("AccountId") - .HasDatabaseName("ix_realms_account_id"); - - b.HasIndex("Slug") - .IsUnique() - .HasDatabaseName("ix_realms_slug"); - - b.ToTable("realms", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.RealmMember", 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.HasIndex("AccountId") - .HasDatabaseName("ix_realm_members_account_id"); - - b.ToTable("realm_members", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.RealmTag", b => - { - b.Property("RealmId") - .HasColumnType("uuid") - .HasColumnName("realm_id"); - - b.Property("TagId") - .HasColumnType("uuid") - .HasColumnName("tag_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("RealmId", "TagId") - .HasName("pk_realm_tags"); - - b.HasIndex("TagId") - .HasDatabaseName("ix_realm_tags_tag_id"); - - b.ToTable("realm_tags", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.Tag", 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("Name") - .IsRequired() - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("name"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_tags"); - - b.ToTable("tags", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.Sticker", 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("Image") - .HasColumnType("jsonb") - .HasColumnName("image"); - - b.Property("ImageId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("image_id"); - - b.Property("PackId") - .HasColumnType("uuid") - .HasColumnName("pack_id"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_stickers"); - - b.HasIndex("PackId") - .HasDatabaseName("ix_stickers_pack_id"); - - b.HasIndex("Slug") - .HasDatabaseName("ix_stickers_slug"); - - b.ToTable("stickers", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.StickerPack", 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("Description") - .IsRequired() - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("Prefix") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("prefix"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_sticker_packs"); - - b.HasIndex("Prefix") - .IsUnique() - .HasDatabaseName("ix_sticker_packs_prefix"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_sticker_packs_publisher_id"); - - b.ToTable("sticker_packs", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFile", b => - { - b.Property("Id") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .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("Description") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property>("FileMeta") - .HasColumnType("jsonb") - .HasColumnName("file_meta"); - - b.Property("HasCompression") - .HasColumnType("boolean") - .HasColumnName("has_compression"); - - b.Property("Hash") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("hash"); - - b.Property("IsMarkedRecycle") - .HasColumnType("boolean") - .HasColumnName("is_marked_recycle"); - - b.Property("MessageId") - .HasColumnType("uuid") - .HasColumnName("message_id"); - - b.Property("MimeType") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("mime_type"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("PostId") - .HasColumnType("uuid") - .HasColumnName("post_id"); - - b.Property>("SensitiveMarks") - .HasColumnType("jsonb") - .HasColumnName("sensitive_marks"); - - b.Property("Size") - .HasColumnType("bigint") - .HasColumnName("size"); - - b.Property("StorageId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("storage_id"); - - b.Property("StorageUrl") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("storage_url"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("UploadedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("uploaded_at"); - - b.Property("UploadedTo") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("uploaded_to"); - - b.Property>("UserMeta") - .HasColumnType("jsonb") - .HasColumnName("user_meta"); - - b.HasKey("Id") - .HasName("pk_files"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_files_account_id"); - - b.HasIndex("MessageId") - .HasDatabaseName("ix_files_message_id"); - - b.HasIndex("PostId") - .HasDatabaseName("ix_files_post_id"); - - b.ToTable("files", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFileReference", 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("ExpiredAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expired_at"); - - b.Property("FileId") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("file_id"); - - b.Property("ResourceId") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("resource_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("Usage") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("usage"); - - b.HasKey("Id") - .HasName("pk_file_references"); - - b.HasIndex("FileId") - .HasDatabaseName("ix_file_references_file_id"); - - b.ToTable("file_references", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Coupon", 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.Sphere.Wallet.Order", 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("IssuerAppId") - .HasColumnType("uuid") - .HasColumnName("issuer_app_id"); - - b.Property>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("PayeeWalletId") - .HasColumnType("uuid") - .HasColumnName("payee_wallet_id"); - - 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("IssuerAppId") - .HasDatabaseName("ix_payment_orders_issuer_app_id"); - - 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.Sphere.Wallet.Subscription", 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.ToTable("wallet_subscriptions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Transaction", 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.Sphere.Wallet.Wallet", 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.Sphere.Wallet.WalletPocket", 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("PostPostCategory", b => - { - b.Property("CategoriesId") - .HasColumnType("uuid") - .HasColumnName("categories_id"); - - b.Property("PostsId") - .HasColumnType("uuid") - .HasColumnName("posts_id"); - - b.HasKey("CategoriesId", "PostsId") - .HasName("pk_post_category_links"); - - b.HasIndex("PostsId") - .HasDatabaseName("ix_post_category_links_posts_id"); - - b.ToTable("post_category_links", (string)null); - }); - - modelBuilder.Entity("PostPostCollection", b => - { - b.Property("CollectionsId") - .HasColumnType("uuid") - .HasColumnName("collections_id"); - - b.Property("PostsId") - .HasColumnType("uuid") - .HasColumnName("posts_id"); - - b.HasKey("CollectionsId", "PostsId") - .HasName("pk_post_collection_links"); - - b.HasIndex("PostsId") - .HasDatabaseName("ix_post_collection_links_posts_id"); - - b.ToTable("post_collection_links", (string)null); - }); - - modelBuilder.Entity("PostPostTag", b => - { - b.Property("PostsId") - .HasColumnType("uuid") - .HasColumnName("posts_id"); - - b.Property("TagsId") - .HasColumnType("uuid") - .HasColumnName("tags_id"); - - b.HasKey("PostsId", "TagsId") - .HasName("pk_post_tag_links"); - - b.HasIndex("TagsId") - .HasDatabaseName("ix_post_tag_links_tags_id"); - - b.ToTable("post_tag_links", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.AbuseReport", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_abuse_reports_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.AccountAuthFactor", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("AuthFactors") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_auth_factors_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.AccountConnection", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Connections") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_connections_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.AccountContact", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Contacts") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_contacts_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.ActionLog", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_action_logs_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Badge", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Badges") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_badges_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.CheckInResult", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_check_in_results_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.MagicSpell", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .HasConstraintName("fk_magic_spells_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Notification", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_notifications_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.NotificationPushSubscription", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_notification_push_subscriptions_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Profile", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithOne("Profile") - .HasForeignKey("DysonNetwork.Sphere.Account.Profile", "AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_profiles_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Relationship", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("OutgoingRelationships") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_relationships_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Account.Account", "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.Sphere.Account.Status", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_statuses_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Challenge", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Challenges") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_challenges_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Session", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Sessions") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_sessions_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Developer.CustomApp", "App") - .WithMany() - .HasForeignKey("AppId") - .HasConstraintName("fk_auth_sessions_custom_apps_app_id"); - - b.HasOne("DysonNetwork.Sphere.Auth.Challenge", "Challenge") - .WithMany() - .HasForeignKey("ChallengeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_sessions_auth_challenges_challenge_id"); - - b.Navigation("Account"); - - b.Navigation("App"); - - b.Navigation("Challenge"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatMember", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_members_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatRoom", "ChatRoom") - .WithMany("Members") - .HasForeignKey("ChatRoomId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_members_chat_rooms_chat_room_id"); - - b.Navigation("Account"); - - b.Navigation("ChatRoom"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatRoom", b => - { - b.HasOne("DysonNetwork.Sphere.Realm.Realm", "Realm") - .WithMany("ChatRooms") - .HasForeignKey("RealmId") - .HasConstraintName("fk_chat_rooms_realms_realm_id"); - - b.Navigation("Realm"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.Message", b => - { - b.HasOne("DysonNetwork.Sphere.Chat.ChatRoom", "ChatRoom") - .WithMany() - .HasForeignKey("ChatRoomId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_messages_chat_rooms_chat_room_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.Message", "ForwardedMessage") - .WithMany() - .HasForeignKey("ForwardedMessageId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_chat_messages_chat_messages_forwarded_message_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.Message", "RepliedMessage") - .WithMany() - .HasForeignKey("RepliedMessageId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_chat_messages_chat_messages_replied_message_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatMember", "Sender") - .WithMany() - .HasForeignKey("SenderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_messages_chat_members_sender_id"); - - b.Navigation("ChatRoom"); - - b.Navigation("ForwardedMessage"); - - b.Navigation("RepliedMessage"); - - b.Navigation("Sender"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.MessageReaction", b => - { - b.HasOne("DysonNetwork.Sphere.Chat.Message", "Message") - .WithMany("Reactions") - .HasForeignKey("MessageId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_reactions_chat_messages_message_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatMember", "Sender") - .WithMany() - .HasForeignKey("SenderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_reactions_chat_members_sender_id"); - - b.Navigation("Message"); - - b.Navigation("Sender"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.RealtimeCall", b => - { - b.HasOne("DysonNetwork.Sphere.Chat.ChatRoom", "Room") - .WithMany() - .HasForeignKey("RoomId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_realtime_call_chat_rooms_room_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatMember", "Sender") - .WithMany() - .HasForeignKey("SenderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_realtime_call_chat_members_sender_id"); - - b.Navigation("Room"); - - b.Navigation("Sender"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Connection.WebReader.WebArticle", b => - { - b.HasOne("DysonNetwork.Sphere.Connection.WebReader.WebFeed", "Feed") - .WithMany("Articles") - .HasForeignKey("FeedId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_web_articles_web_feeds_feed_id"); - - b.Navigation("Feed"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Connection.WebReader.WebFeed", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany() - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_web_feeds_publishers_publisher_id"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomApp", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Developer") - .WithMany() - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_custom_apps_publishers_publisher_id"); - - b.Navigation("Developer"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomAppSecret", b => - { - b.HasOne("DysonNetwork.Sphere.Developer.CustomApp", "App") - .WithMany("Secrets") - .HasForeignKey("AppId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_custom_app_secrets_custom_apps_app_id"); - - b.Navigation("App"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroupMember", b => - { - b.HasOne("DysonNetwork.Sphere.Permission.PermissionGroup", "Group") - .WithMany("Members") - .HasForeignKey("GroupId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_permission_group_members_permission_groups_group_id"); - - b.Navigation("Group"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionNode", b => - { - b.HasOne("DysonNetwork.Sphere.Permission.PermissionGroup", "Group") - .WithMany("Nodes") - .HasForeignKey("GroupId") - .HasConstraintName("fk_permission_nodes_permission_groups_group_id"); - - b.Navigation("Group"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.Post", b => - { - b.HasOne("DysonNetwork.Sphere.Post.Post", "ForwardedPost") - .WithMany() - .HasForeignKey("ForwardedPostId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_posts_posts_forwarded_post_id"); - - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Posts") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_posts_publishers_publisher_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", "RepliedPost") - .WithMany() - .HasForeignKey("RepliedPostId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_posts_posts_replied_post_id"); - - b.Navigation("ForwardedPost"); - - b.Navigation("Publisher"); - - b.Navigation("RepliedPost"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostCollection", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Collections") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_collections_publishers_publisher_id"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostReaction", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_reactions_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", "Post") - .WithMany("Reactions") - .HasForeignKey("PostId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_reactions_posts_post_id"); - - b.Navigation("Account"); - - b.Navigation("Post"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.Publisher", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .HasConstraintName("fk_publishers_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Realm.Realm", "Realm") - .WithMany() - .HasForeignKey("RealmId") - .HasConstraintName("fk_publishers_realms_realm_id"); - - b.Navigation("Account"); - - b.Navigation("Realm"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherFeature", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany() - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_features_publishers_publisher_id"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherMember", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_members_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Members") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_members_publishers_publisher_id"); - - b.Navigation("Account"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherSubscription", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_subscriptions_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Subscriptions") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_subscriptions_publishers_publisher_id"); - - b.Navigation("Account"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.Realm", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_realms_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.RealmMember", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_realm_members_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Realm.Realm", "Realm") - .WithMany("Members") - .HasForeignKey("RealmId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_realm_members_realms_realm_id"); - - b.Navigation("Account"); - - b.Navigation("Realm"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.RealmTag", b => - { - b.HasOne("DysonNetwork.Sphere.Realm.Realm", "Realm") - .WithMany("RealmTags") - .HasForeignKey("RealmId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_realm_tags_realms_realm_id"); - - b.HasOne("DysonNetwork.Sphere.Realm.Tag", "Tag") - .WithMany("RealmTags") - .HasForeignKey("TagId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_realm_tags_tags_tag_id"); - - b.Navigation("Realm"); - - b.Navigation("Tag"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.Sticker", b => - { - b.HasOne("DysonNetwork.Sphere.Sticker.StickerPack", "Pack") - .WithMany() - .HasForeignKey("PackId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_stickers_sticker_packs_pack_id"); - - b.Navigation("Pack"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.StickerPack", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany() - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_sticker_packs_publishers_publisher_id"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFile", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_files_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.Message", null) - .WithMany("OutdatedAttachments") - .HasForeignKey("MessageId") - .HasConstraintName("fk_files_chat_messages_message_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany("OutdatedAttachments") - .HasForeignKey("PostId") - .HasConstraintName("fk_files_posts_post_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFileReference", b => - { - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "File") - .WithMany() - .HasForeignKey("FileId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_file_references_files_file_id"); - - b.Navigation("File"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Order", b => - { - b.HasOne("DysonNetwork.Sphere.Developer.CustomApp", "IssuerApp") - .WithMany() - .HasForeignKey("IssuerAppId") - .HasConstraintName("fk_payment_orders_custom_apps_issuer_app_id"); - - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "PayeeWallet") - .WithMany() - .HasForeignKey("PayeeWalletId") - .HasConstraintName("fk_payment_orders_wallets_payee_wallet_id"); - - b.HasOne("DysonNetwork.Sphere.Wallet.Transaction", "Transaction") - .WithMany() - .HasForeignKey("TransactionId") - .HasConstraintName("fk_payment_orders_payment_transactions_transaction_id"); - - b.Navigation("IssuerApp"); - - b.Navigation("PayeeWallet"); - - b.Navigation("Transaction"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Subscription", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Subscriptions") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_wallet_subscriptions_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Wallet.Coupon", "Coupon") - .WithMany() - .HasForeignKey("CouponId") - .HasConstraintName("fk_wallet_subscriptions_wallet_coupons_coupon_id"); - - b.Navigation("Account"); - - b.Navigation("Coupon"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Transaction", b => - { - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "PayeeWallet") - .WithMany() - .HasForeignKey("PayeeWalletId") - .HasConstraintName("fk_payment_transactions_wallets_payee_wallet_id"); - - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "PayerWallet") - .WithMany() - .HasForeignKey("PayerWalletId") - .HasConstraintName("fk_payment_transactions_wallets_payer_wallet_id"); - - b.Navigation("PayeeWallet"); - - b.Navigation("PayerWallet"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Wallet", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_wallets_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.WalletPocket", b => - { - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "Wallet") - .WithMany("Pockets") - .HasForeignKey("WalletId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_wallet_pockets_wallets_wallet_id"); - - b.Navigation("Wallet"); - }); - - modelBuilder.Entity("PostPostCategory", b => - { - b.HasOne("DysonNetwork.Sphere.Post.PostCategory", null) - .WithMany() - .HasForeignKey("CategoriesId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_category_links_post_categories_categories_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany() - .HasForeignKey("PostsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_category_links_posts_posts_id"); - }); - - modelBuilder.Entity("PostPostCollection", b => - { - b.HasOne("DysonNetwork.Sphere.Post.PostCollection", null) - .WithMany() - .HasForeignKey("CollectionsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_collection_links_post_collections_collections_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany() - .HasForeignKey("PostsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_collection_links_posts_posts_id"); - }); - - modelBuilder.Entity("PostPostTag", b => - { - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany() - .HasForeignKey("PostsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_tag_links_posts_posts_id"); - - b.HasOne("DysonNetwork.Sphere.Post.PostTag", null) - .WithMany() - .HasForeignKey("TagsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_tag_links_post_tags_tags_id"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Account", 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"); - - b.Navigation("Subscriptions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatRoom", b => - { - b.Navigation("Members"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.Message", b => - { - b.Navigation("OutdatedAttachments"); - - b.Navigation("Reactions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Connection.WebReader.WebFeed", b => - { - b.Navigation("Articles"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomApp", b => - { - b.Navigation("Secrets"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroup", b => - { - b.Navigation("Members"); - - b.Navigation("Nodes"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.Post", b => - { - b.Navigation("OutdatedAttachments"); - - b.Navigation("Reactions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.Publisher", b => - { - b.Navigation("Collections"); - - b.Navigation("Members"); - - b.Navigation("Posts"); - - b.Navigation("Subscriptions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.Realm", b => - { - b.Navigation("ChatRooms"); - - b.Navigation("Members"); - - b.Navigation("RealmTags"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.Tag", b => - { - b.Navigation("RealmTags"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Wallet", b => - { - b.Navigation("Pockets"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/DysonNetwork.Sphere/Data/Migrations/20250629084150_AuthSessionWithApp.cs b/DysonNetwork.Sphere/Data/Migrations/20250629084150_AuthSessionWithApp.cs deleted file mode 100644 index 80d7f52..0000000 --- a/DysonNetwork.Sphere/Data/Migrations/20250629084150_AuthSessionWithApp.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace DysonNetwork.Sphere.Data.Migrations -{ - /// - public partial class AuthSessionWithApp : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "app_id", - table: "auth_sessions", - type: "uuid", - nullable: true); - - migrationBuilder.CreateIndex( - name: "ix_auth_sessions_app_id", - table: "auth_sessions", - column: "app_id"); - - migrationBuilder.AddForeignKey( - name: "fk_auth_sessions_custom_apps_app_id", - table: "auth_sessions", - column: "app_id", - principalTable: "custom_apps", - principalColumn: "id"); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropForeignKey( - name: "fk_auth_sessions_custom_apps_app_id", - table: "auth_sessions"); - - migrationBuilder.DropIndex( - name: "ix_auth_sessions_app_id", - table: "auth_sessions"); - - migrationBuilder.DropColumn( - name: "app_id", - table: "auth_sessions"); - } - } -} diff --git a/DysonNetwork.Sphere/Data/Migrations/20250629123136_CustomAppsRefine.Designer.cs b/DysonNetwork.Sphere/Data/Migrations/20250629123136_CustomAppsRefine.Designer.cs deleted file mode 100644 index 5a31d1a..0000000 --- a/DysonNetwork.Sphere/Data/Migrations/20250629123136_CustomAppsRefine.Designer.cs +++ /dev/null @@ -1,3993 +0,0 @@ -// -using System; -using System.Collections.Generic; -using System.Text.Json; -using DysonNetwork.Sphere; -using DysonNetwork.Sphere.Account; -using DysonNetwork.Sphere.Chat; -using DysonNetwork.Sphere.Connection.WebReader; -using DysonNetwork.Sphere.Developer; -using DysonNetwork.Sphere.Storage; -using DysonNetwork.Sphere.Wallet; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using NetTopologySuite.Geometries; -using NodaTime; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; -using NpgsqlTypes; - -#nullable disable - -namespace DysonNetwork.Sphere.Data.Migrations -{ - [DbContext(typeof(AppDatabase))] - [Migration("20250629123136_CustomAppsRefine")] - partial class CustomAppsRefine - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "9.0.3") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "postgis"); - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.AbuseReport", 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.Sphere.Account.Account", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("ActivatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("activated_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("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("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.Sphere.Account.AccountAuthFactor", 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.Sphere.Account.AccountConnection", 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.Sphere.Account.AccountContact", 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("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.Sphere.Account.ActionLog", 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("geometry") - .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.Sphere.Account.Badge", 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.Sphere.Account.CheckInResult", 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("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.Sphere.Account.MagicSpell", 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.Sphere.Account.Notification", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("Content") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .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>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("Priority") - .HasColumnType("integer") - .HasColumnName("priority"); - - b.Property("Subtitle") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("subtitle"); - - b.Property("Title") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("title"); - - b.Property("Topic") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("topic"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("ViewedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("viewed_at"); - - b.HasKey("Id") - .HasName("pk_notifications"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_notifications_account_id"); - - b.ToTable("notifications", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.NotificationPushSubscription", 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(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("device_id"); - - b.Property("DeviceToken") - .IsRequired() - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("device_token"); - - b.Property("LastUsedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_used_at"); - - b.Property("Provider") - .HasColumnType("integer") - .HasColumnName("provider"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_notification_push_subscriptions"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_notification_push_subscriptions_account_id"); - - b.HasIndex("DeviceToken", "DeviceId", "AccountId") - .IsUnique() - .HasDatabaseName("ix_notification_push_subscriptions_device_token_device_id_acco"); - - b.ToTable("notification_push_subscriptions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Profile", 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("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_id"); - - 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("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("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - b.Property("Pronouns") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("pronouns"); - - b.Property("StellarMembership") - .HasColumnType("jsonb") - .HasColumnName("stellar_membership"); - - 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("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.Sphere.Account.Relationship", 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.Sphere.Account.Status", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - 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("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("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.Sphere.Auth.Challenge", 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") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("device_id"); - - 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("geometry") - .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("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_challenges"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_auth_challenges_account_id"); - - b.ToTable("auth_challenges", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Session", 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("ChallengeId") - .HasColumnType("uuid") - .HasColumnName("challenge_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("Label") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("label"); - - b.Property("LastGrantedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_granted_at"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_auth_sessions"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_auth_sessions_account_id"); - - b.HasIndex("AppId") - .HasDatabaseName("ix_auth_sessions_app_id"); - - b.HasIndex("ChallengeId") - .HasDatabaseName("ix_auth_sessions_challenge_id"); - - b.ToTable("auth_sessions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatMember", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("BreakUntil") - .HasColumnType("timestamp with time zone") - .HasColumnName("break_until"); - - b.Property("ChatRoomId") - .HasColumnType("uuid") - .HasColumnName("chat_room_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("IsBot") - .HasColumnType("boolean") - .HasColumnName("is_bot"); - - b.Property("JoinedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("joined_at"); - - b.Property("LastReadAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_read_at"); - - b.Property("LeaveAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("leave_at"); - - b.Property("Nick") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("nick"); - - b.Property("Notify") - .HasColumnType("integer") - .HasColumnName("notify"); - - b.Property("Role") - .HasColumnType("integer") - .HasColumnName("role"); - - b.Property("TimeoutCause") - .HasColumnType("jsonb") - .HasColumnName("timeout_cause"); - - b.Property("TimeoutUntil") - .HasColumnType("timestamp with time zone") - .HasColumnName("timeout_until"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_chat_members"); - - b.HasAlternateKey("ChatRoomId", "AccountId") - .HasName("ak_chat_members_chat_room_id_account_id"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_chat_members_account_id"); - - b.ToTable("chat_members", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatRoom", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("Background") - .HasColumnType("jsonb") - .HasColumnName("background"); - - b.Property("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_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("Description") - .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") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("Picture") - .HasColumnType("jsonb") - .HasColumnName("picture"); - - b.Property("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - b.Property("RealmId") - .HasColumnType("uuid") - .HasColumnName("realm_id"); - - b.Property("Type") - .HasColumnType("integer") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_chat_rooms"); - - b.HasIndex("RealmId") - .HasDatabaseName("ix_chat_rooms_realm_id"); - - b.ToTable("chat_rooms", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.Message", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property>("Attachments") - .IsRequired() - .HasColumnType("jsonb") - .HasColumnName("attachments"); - - b.Property("ChatRoomId") - .HasColumnType("uuid") - .HasColumnName("chat_room_id"); - - b.Property("Content") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .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("EditedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("edited_at"); - - b.Property("ForwardedMessageId") - .HasColumnType("uuid") - .HasColumnName("forwarded_message_id"); - - b.Property>("MembersMentioned") - .HasColumnType("jsonb") - .HasColumnName("members_mentioned"); - - b.Property>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("Nonce") - .IsRequired() - .HasMaxLength(36) - .HasColumnType("character varying(36)") - .HasColumnName("nonce"); - - b.Property("RepliedMessageId") - .HasColumnType("uuid") - .HasColumnName("replied_message_id"); - - b.Property("SenderId") - .HasColumnType("uuid") - .HasColumnName("sender_id"); - - 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_chat_messages"); - - b.HasIndex("ChatRoomId") - .HasDatabaseName("ix_chat_messages_chat_room_id"); - - b.HasIndex("ForwardedMessageId") - .HasDatabaseName("ix_chat_messages_forwarded_message_id"); - - b.HasIndex("RepliedMessageId") - .HasDatabaseName("ix_chat_messages_replied_message_id"); - - b.HasIndex("SenderId") - .HasDatabaseName("ix_chat_messages_sender_id"); - - b.ToTable("chat_messages", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.MessageReaction", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("Attitude") - .HasColumnType("integer") - .HasColumnName("attitude"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("MessageId") - .HasColumnType("uuid") - .HasColumnName("message_id"); - - b.Property("SenderId") - .HasColumnType("uuid") - .HasColumnName("sender_id"); - - b.Property("Symbol") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("symbol"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_chat_reactions"); - - b.HasIndex("MessageId") - .HasDatabaseName("ix_chat_reactions_message_id"); - - b.HasIndex("SenderId") - .HasDatabaseName("ix_chat_reactions_sender_id"); - - b.ToTable("chat_reactions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.RealtimeCall", 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("EndedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("ended_at"); - - b.Property("ProviderName") - .HasColumnType("text") - .HasColumnName("provider_name"); - - b.Property("RoomId") - .HasColumnType("uuid") - .HasColumnName("room_id"); - - b.Property("SenderId") - .HasColumnType("uuid") - .HasColumnName("sender_id"); - - b.Property("SessionId") - .HasColumnType("text") - .HasColumnName("session_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("UpstreamConfigJson") - .HasColumnType("jsonb") - .HasColumnName("upstream"); - - b.HasKey("Id") - .HasName("pk_chat_realtime_call"); - - b.HasIndex("RoomId") - .HasDatabaseName("ix_chat_realtime_call_room_id"); - - b.HasIndex("SenderId") - .HasDatabaseName("ix_chat_realtime_call_sender_id"); - - b.ToTable("chat_realtime_call", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Connection.WebReader.WebArticle", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("Author") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("author"); - - b.Property("Content") - .HasColumnType("text") - .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("FeedId") - .HasColumnType("uuid") - .HasColumnName("feed_id"); - - b.Property>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("Preview") - .HasColumnType("jsonb") - .HasColumnName("preview"); - - b.Property("PublishedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("published_at"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("title"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("Url") - .IsRequired() - .HasMaxLength(8192) - .HasColumnType("character varying(8192)") - .HasColumnName("url"); - - b.HasKey("Id") - .HasName("pk_web_articles"); - - b.HasIndex("FeedId") - .HasDatabaseName("ix_web_articles_feed_id"); - - b.HasIndex("Url") - .IsUnique() - .HasDatabaseName("ix_web_articles_url"); - - b.ToTable("web_articles", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Connection.WebReader.WebFeed", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("Config") - .IsRequired() - .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("Description") - .HasMaxLength(8192) - .HasColumnType("character varying(8192)") - .HasColumnName("description"); - - b.Property("Preview") - .HasColumnType("jsonb") - .HasColumnName("preview"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("title"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("Url") - .IsRequired() - .HasMaxLength(8192) - .HasColumnType("character varying(8192)") - .HasColumnName("url"); - - b.HasKey("Id") - .HasName("pk_web_feeds"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_web_feeds_publisher_id"); - - b.HasIndex("Url") - .IsUnique() - .HasDatabaseName("ix_web_feeds_url"); - - b.ToTable("web_feeds", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomApp", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("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") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("Links") - .HasColumnType("jsonb") - .HasColumnName("links"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("OauthConfig") - .HasColumnType("jsonb") - .HasColumnName("oauth_config"); - - b.Property("Picture") - .HasColumnType("jsonb") - .HasColumnName("picture"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("slug"); - - b.Property("Status") - .HasColumnType("integer") - .HasColumnName("status"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("Verification") - .HasColumnType("jsonb") - .HasColumnName("verification"); - - b.HasKey("Id") - .HasName("pk_custom_apps"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_custom_apps_publisher_id"); - - b.ToTable("custom_apps", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomAppSecret", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AppId") - .HasColumnType("uuid") - .HasColumnName("app_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("Description") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("ExpiredAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expired_at"); - - b.Property("IsOidc") - .HasColumnType("boolean") - .HasColumnName("is_oidc"); - - b.Property("Secret") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("secret"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_custom_app_secrets"); - - b.HasIndex("AppId") - .HasDatabaseName("ix_custom_app_secrets_app_id"); - - b.HasIndex("Secret") - .IsUnique() - .HasDatabaseName("ix_custom_app_secrets_secret"); - - b.ToTable("custom_app_secrets", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroup", 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.Sphere.Permission.PermissionGroupMember", 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.Sphere.Permission.PermissionNode", 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("Area") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("area"); - - 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("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", "Area", "Actor") - .HasDatabaseName("ix_permission_nodes_key_area_actor"); - - b.ToTable("permission_nodes", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.Post", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property>("Attachments") - .IsRequired() - .HasColumnType("jsonb") - .HasColumnName("attachments"); - - b.Property("Content") - .HasColumnType("text") - .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("Description") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("Downvotes") - .HasColumnType("integer") - .HasColumnName("downvotes"); - - b.Property("EditedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("edited_at"); - - b.Property("ForwardedPostId") - .HasColumnType("uuid") - .HasColumnName("forwarded_post_id"); - - b.Property("Language") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("language"); - - b.Property>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("PublishedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("published_at"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("RepliedPostId") - .HasColumnType("uuid") - .HasColumnName("replied_post_id"); - - b.Property("SearchVector") - .IsRequired() - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("tsvector") - .HasColumnName("search_vector") - .HasAnnotation("Npgsql:TsVectorConfig", "simple") - .HasAnnotation("Npgsql:TsVectorProperties", new[] { "Title", "Description", "Content" }); - - b.Property>("SensitiveMarks") - .HasColumnType("jsonb") - .HasColumnName("sensitive_marks"); - - b.Property("Title") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("title"); - - b.Property("Type") - .HasColumnType("integer") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("Upvotes") - .HasColumnType("integer") - .HasColumnName("upvotes"); - - b.Property("ViewsTotal") - .HasColumnType("integer") - .HasColumnName("views_total"); - - b.Property("ViewsUnique") - .HasColumnType("integer") - .HasColumnName("views_unique"); - - b.Property("Visibility") - .HasColumnType("integer") - .HasColumnName("visibility"); - - b.HasKey("Id") - .HasName("pk_posts"); - - b.HasIndex("ForwardedPostId") - .HasDatabaseName("ix_posts_forwarded_post_id"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_posts_publisher_id"); - - b.HasIndex("RepliedPostId") - .HasDatabaseName("ix_posts_replied_post_id"); - - b.HasIndex("SearchVector") - .HasDatabaseName("ix_posts_search_vector"); - - NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex("SearchVector"), "GIN"); - - b.ToTable("posts", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostCategory", 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("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("name"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_categories"); - - b.ToTable("post_categories", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostCollection", 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("Description") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("name"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_collections"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_post_collections_publisher_id"); - - b.ToTable("post_collections", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostReaction", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("Attitude") - .HasColumnType("integer") - .HasColumnName("attitude"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("PostId") - .HasColumnType("uuid") - .HasColumnName("post_id"); - - b.Property("Symbol") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("symbol"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_reactions"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_post_reactions_account_id"); - - b.HasIndex("PostId") - .HasDatabaseName("ix_post_reactions_post_id"); - - b.ToTable("post_reactions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostTag", 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("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("name"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_tags"); - - b.ToTable("post_tags", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.Publisher", 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("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_id"); - - b.Property("Bio") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("bio"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - 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("Picture") - .HasColumnType("jsonb") - .HasColumnName("picture"); - - b.Property("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - b.Property("RealmId") - .HasColumnType("uuid") - .HasColumnName("realm_id"); - - b.Property("Type") - .HasColumnType("integer") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("Verification") - .HasColumnType("jsonb") - .HasColumnName("verification"); - - b.HasKey("Id") - .HasName("pk_publishers"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_publishers_account_id"); - - b.HasIndex("Name") - .IsUnique() - .HasDatabaseName("ix_publishers_name"); - - b.HasIndex("RealmId") - .HasDatabaseName("ix_publishers_realm_id"); - - b.ToTable("publishers", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherFeature", 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("ExpiredAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expired_at"); - - b.Property("Flag") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("flag"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_publisher_features"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_publisher_features_publisher_id"); - - b.ToTable("publisher_features", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherMember", b => - { - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_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("Role") - .HasColumnType("integer") - .HasColumnName("role"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("PublisherId", "AccountId") - .HasName("pk_publisher_members"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_publisher_members_account_id"); - - b.ToTable("publisher_members", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherSubscription", 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("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("Status") - .HasColumnType("integer") - .HasColumnName("status"); - - b.Property("Tier") - .HasColumnType("integer") - .HasColumnName("tier"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_publisher_subscriptions"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_publisher_subscriptions_account_id"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_publisher_subscriptions_publisher_id"); - - b.ToTable("publisher_subscriptions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.Realm", 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("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_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("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("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - 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("AccountId") - .HasDatabaseName("ix_realms_account_id"); - - b.HasIndex("Slug") - .IsUnique() - .HasDatabaseName("ix_realms_slug"); - - b.ToTable("realms", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.RealmMember", 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.HasIndex("AccountId") - .HasDatabaseName("ix_realm_members_account_id"); - - b.ToTable("realm_members", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.RealmTag", b => - { - b.Property("RealmId") - .HasColumnType("uuid") - .HasColumnName("realm_id"); - - b.Property("TagId") - .HasColumnType("uuid") - .HasColumnName("tag_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("RealmId", "TagId") - .HasName("pk_realm_tags"); - - b.HasIndex("TagId") - .HasDatabaseName("ix_realm_tags_tag_id"); - - b.ToTable("realm_tags", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.Tag", 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("Name") - .IsRequired() - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("name"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_tags"); - - b.ToTable("tags", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.Sticker", 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("Image") - .HasColumnType("jsonb") - .HasColumnName("image"); - - b.Property("ImageId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("image_id"); - - b.Property("PackId") - .HasColumnType("uuid") - .HasColumnName("pack_id"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_stickers"); - - b.HasIndex("PackId") - .HasDatabaseName("ix_stickers_pack_id"); - - b.HasIndex("Slug") - .HasDatabaseName("ix_stickers_slug"); - - b.ToTable("stickers", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.StickerPack", 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("Description") - .IsRequired() - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("Prefix") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("prefix"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_sticker_packs"); - - b.HasIndex("Prefix") - .IsUnique() - .HasDatabaseName("ix_sticker_packs_prefix"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_sticker_packs_publisher_id"); - - b.ToTable("sticker_packs", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFile", b => - { - b.Property("Id") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .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("Description") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property>("FileMeta") - .HasColumnType("jsonb") - .HasColumnName("file_meta"); - - b.Property("HasCompression") - .HasColumnType("boolean") - .HasColumnName("has_compression"); - - b.Property("Hash") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("hash"); - - b.Property("IsMarkedRecycle") - .HasColumnType("boolean") - .HasColumnName("is_marked_recycle"); - - b.Property("MessageId") - .HasColumnType("uuid") - .HasColumnName("message_id"); - - b.Property("MimeType") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("mime_type"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("PostId") - .HasColumnType("uuid") - .HasColumnName("post_id"); - - b.Property>("SensitiveMarks") - .HasColumnType("jsonb") - .HasColumnName("sensitive_marks"); - - b.Property("Size") - .HasColumnType("bigint") - .HasColumnName("size"); - - b.Property("StorageId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("storage_id"); - - b.Property("StorageUrl") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("storage_url"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("UploadedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("uploaded_at"); - - b.Property("UploadedTo") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("uploaded_to"); - - b.Property>("UserMeta") - .HasColumnType("jsonb") - .HasColumnName("user_meta"); - - b.HasKey("Id") - .HasName("pk_files"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_files_account_id"); - - b.HasIndex("MessageId") - .HasDatabaseName("ix_files_message_id"); - - b.HasIndex("PostId") - .HasDatabaseName("ix_files_post_id"); - - b.ToTable("files", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFileReference", 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("ExpiredAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expired_at"); - - b.Property("FileId") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("file_id"); - - b.Property("ResourceId") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("resource_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("Usage") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("usage"); - - b.HasKey("Id") - .HasName("pk_file_references"); - - b.HasIndex("FileId") - .HasDatabaseName("ix_file_references_file_id"); - - b.ToTable("file_references", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Coupon", 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.Sphere.Wallet.Order", 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("IssuerAppId") - .HasColumnType("uuid") - .HasColumnName("issuer_app_id"); - - b.Property>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("PayeeWalletId") - .HasColumnType("uuid") - .HasColumnName("payee_wallet_id"); - - 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("IssuerAppId") - .HasDatabaseName("ix_payment_orders_issuer_app_id"); - - 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.Sphere.Wallet.Subscription", 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.ToTable("wallet_subscriptions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Transaction", 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.Sphere.Wallet.Wallet", 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.Sphere.Wallet.WalletPocket", 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("PostPostCategory", b => - { - b.Property("CategoriesId") - .HasColumnType("uuid") - .HasColumnName("categories_id"); - - b.Property("PostsId") - .HasColumnType("uuid") - .HasColumnName("posts_id"); - - b.HasKey("CategoriesId", "PostsId") - .HasName("pk_post_category_links"); - - b.HasIndex("PostsId") - .HasDatabaseName("ix_post_category_links_posts_id"); - - b.ToTable("post_category_links", (string)null); - }); - - modelBuilder.Entity("PostPostCollection", b => - { - b.Property("CollectionsId") - .HasColumnType("uuid") - .HasColumnName("collections_id"); - - b.Property("PostsId") - .HasColumnType("uuid") - .HasColumnName("posts_id"); - - b.HasKey("CollectionsId", "PostsId") - .HasName("pk_post_collection_links"); - - b.HasIndex("PostsId") - .HasDatabaseName("ix_post_collection_links_posts_id"); - - b.ToTable("post_collection_links", (string)null); - }); - - modelBuilder.Entity("PostPostTag", b => - { - b.Property("PostsId") - .HasColumnType("uuid") - .HasColumnName("posts_id"); - - b.Property("TagsId") - .HasColumnType("uuid") - .HasColumnName("tags_id"); - - b.HasKey("PostsId", "TagsId") - .HasName("pk_post_tag_links"); - - b.HasIndex("TagsId") - .HasDatabaseName("ix_post_tag_links_tags_id"); - - b.ToTable("post_tag_links", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.AbuseReport", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_abuse_reports_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.AccountAuthFactor", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("AuthFactors") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_auth_factors_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.AccountConnection", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Connections") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_connections_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.AccountContact", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Contacts") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_contacts_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.ActionLog", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_action_logs_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Badge", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Badges") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_badges_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.CheckInResult", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_check_in_results_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.MagicSpell", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .HasConstraintName("fk_magic_spells_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Notification", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_notifications_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.NotificationPushSubscription", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_notification_push_subscriptions_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Profile", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithOne("Profile") - .HasForeignKey("DysonNetwork.Sphere.Account.Profile", "AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_profiles_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Relationship", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("OutgoingRelationships") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_relationships_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Account.Account", "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.Sphere.Account.Status", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_statuses_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Challenge", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Challenges") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_challenges_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Session", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Sessions") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_sessions_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Developer.CustomApp", "App") - .WithMany() - .HasForeignKey("AppId") - .HasConstraintName("fk_auth_sessions_custom_apps_app_id"); - - b.HasOne("DysonNetwork.Sphere.Auth.Challenge", "Challenge") - .WithMany() - .HasForeignKey("ChallengeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_sessions_auth_challenges_challenge_id"); - - b.Navigation("Account"); - - b.Navigation("App"); - - b.Navigation("Challenge"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatMember", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_members_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatRoom", "ChatRoom") - .WithMany("Members") - .HasForeignKey("ChatRoomId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_members_chat_rooms_chat_room_id"); - - b.Navigation("Account"); - - b.Navigation("ChatRoom"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatRoom", b => - { - b.HasOne("DysonNetwork.Sphere.Realm.Realm", "Realm") - .WithMany("ChatRooms") - .HasForeignKey("RealmId") - .HasConstraintName("fk_chat_rooms_realms_realm_id"); - - b.Navigation("Realm"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.Message", b => - { - b.HasOne("DysonNetwork.Sphere.Chat.ChatRoom", "ChatRoom") - .WithMany() - .HasForeignKey("ChatRoomId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_messages_chat_rooms_chat_room_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.Message", "ForwardedMessage") - .WithMany() - .HasForeignKey("ForwardedMessageId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_chat_messages_chat_messages_forwarded_message_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.Message", "RepliedMessage") - .WithMany() - .HasForeignKey("RepliedMessageId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_chat_messages_chat_messages_replied_message_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatMember", "Sender") - .WithMany() - .HasForeignKey("SenderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_messages_chat_members_sender_id"); - - b.Navigation("ChatRoom"); - - b.Navigation("ForwardedMessage"); - - b.Navigation("RepliedMessage"); - - b.Navigation("Sender"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.MessageReaction", b => - { - b.HasOne("DysonNetwork.Sphere.Chat.Message", "Message") - .WithMany("Reactions") - .HasForeignKey("MessageId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_reactions_chat_messages_message_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatMember", "Sender") - .WithMany() - .HasForeignKey("SenderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_reactions_chat_members_sender_id"); - - b.Navigation("Message"); - - b.Navigation("Sender"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.RealtimeCall", b => - { - b.HasOne("DysonNetwork.Sphere.Chat.ChatRoom", "Room") - .WithMany() - .HasForeignKey("RoomId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_realtime_call_chat_rooms_room_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatMember", "Sender") - .WithMany() - .HasForeignKey("SenderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_realtime_call_chat_members_sender_id"); - - b.Navigation("Room"); - - b.Navigation("Sender"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Connection.WebReader.WebArticle", b => - { - b.HasOne("DysonNetwork.Sphere.Connection.WebReader.WebFeed", "Feed") - .WithMany("Articles") - .HasForeignKey("FeedId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_web_articles_web_feeds_feed_id"); - - b.Navigation("Feed"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Connection.WebReader.WebFeed", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany() - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_web_feeds_publishers_publisher_id"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomApp", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Developer") - .WithMany() - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_custom_apps_publishers_publisher_id"); - - b.Navigation("Developer"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomAppSecret", b => - { - b.HasOne("DysonNetwork.Sphere.Developer.CustomApp", "App") - .WithMany("Secrets") - .HasForeignKey("AppId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_custom_app_secrets_custom_apps_app_id"); - - b.Navigation("App"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroupMember", b => - { - b.HasOne("DysonNetwork.Sphere.Permission.PermissionGroup", "Group") - .WithMany("Members") - .HasForeignKey("GroupId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_permission_group_members_permission_groups_group_id"); - - b.Navigation("Group"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionNode", b => - { - b.HasOne("DysonNetwork.Sphere.Permission.PermissionGroup", "Group") - .WithMany("Nodes") - .HasForeignKey("GroupId") - .HasConstraintName("fk_permission_nodes_permission_groups_group_id"); - - b.Navigation("Group"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.Post", b => - { - b.HasOne("DysonNetwork.Sphere.Post.Post", "ForwardedPost") - .WithMany() - .HasForeignKey("ForwardedPostId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_posts_posts_forwarded_post_id"); - - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Posts") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_posts_publishers_publisher_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", "RepliedPost") - .WithMany() - .HasForeignKey("RepliedPostId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_posts_posts_replied_post_id"); - - b.Navigation("ForwardedPost"); - - b.Navigation("Publisher"); - - b.Navigation("RepliedPost"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostCollection", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Collections") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_collections_publishers_publisher_id"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostReaction", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_reactions_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", "Post") - .WithMany("Reactions") - .HasForeignKey("PostId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_reactions_posts_post_id"); - - b.Navigation("Account"); - - b.Navigation("Post"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.Publisher", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .HasConstraintName("fk_publishers_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Realm.Realm", "Realm") - .WithMany() - .HasForeignKey("RealmId") - .HasConstraintName("fk_publishers_realms_realm_id"); - - b.Navigation("Account"); - - b.Navigation("Realm"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherFeature", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Features") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_features_publishers_publisher_id"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherMember", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_members_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Members") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_members_publishers_publisher_id"); - - b.Navigation("Account"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherSubscription", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_subscriptions_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Subscriptions") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_subscriptions_publishers_publisher_id"); - - b.Navigation("Account"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.Realm", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_realms_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.RealmMember", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_realm_members_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Realm.Realm", "Realm") - .WithMany("Members") - .HasForeignKey("RealmId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_realm_members_realms_realm_id"); - - b.Navigation("Account"); - - b.Navigation("Realm"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.RealmTag", b => - { - b.HasOne("DysonNetwork.Sphere.Realm.Realm", "Realm") - .WithMany("RealmTags") - .HasForeignKey("RealmId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_realm_tags_realms_realm_id"); - - b.HasOne("DysonNetwork.Sphere.Realm.Tag", "Tag") - .WithMany("RealmTags") - .HasForeignKey("TagId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_realm_tags_tags_tag_id"); - - b.Navigation("Realm"); - - b.Navigation("Tag"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.Sticker", b => - { - b.HasOne("DysonNetwork.Sphere.Sticker.StickerPack", "Pack") - .WithMany() - .HasForeignKey("PackId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_stickers_sticker_packs_pack_id"); - - b.Navigation("Pack"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.StickerPack", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany() - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_sticker_packs_publishers_publisher_id"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFile", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_files_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.Message", null) - .WithMany("OutdatedAttachments") - .HasForeignKey("MessageId") - .HasConstraintName("fk_files_chat_messages_message_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany("OutdatedAttachments") - .HasForeignKey("PostId") - .HasConstraintName("fk_files_posts_post_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFileReference", b => - { - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "File") - .WithMany() - .HasForeignKey("FileId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_file_references_files_file_id"); - - b.Navigation("File"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Order", b => - { - b.HasOne("DysonNetwork.Sphere.Developer.CustomApp", "IssuerApp") - .WithMany() - .HasForeignKey("IssuerAppId") - .HasConstraintName("fk_payment_orders_custom_apps_issuer_app_id"); - - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "PayeeWallet") - .WithMany() - .HasForeignKey("PayeeWalletId") - .HasConstraintName("fk_payment_orders_wallets_payee_wallet_id"); - - b.HasOne("DysonNetwork.Sphere.Wallet.Transaction", "Transaction") - .WithMany() - .HasForeignKey("TransactionId") - .HasConstraintName("fk_payment_orders_payment_transactions_transaction_id"); - - b.Navigation("IssuerApp"); - - b.Navigation("PayeeWallet"); - - b.Navigation("Transaction"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Subscription", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Subscriptions") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_wallet_subscriptions_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Wallet.Coupon", "Coupon") - .WithMany() - .HasForeignKey("CouponId") - .HasConstraintName("fk_wallet_subscriptions_wallet_coupons_coupon_id"); - - b.Navigation("Account"); - - b.Navigation("Coupon"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Transaction", b => - { - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "PayeeWallet") - .WithMany() - .HasForeignKey("PayeeWalletId") - .HasConstraintName("fk_payment_transactions_wallets_payee_wallet_id"); - - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "PayerWallet") - .WithMany() - .HasForeignKey("PayerWalletId") - .HasConstraintName("fk_payment_transactions_wallets_payer_wallet_id"); - - b.Navigation("PayeeWallet"); - - b.Navigation("PayerWallet"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Wallet", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_wallets_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.WalletPocket", b => - { - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "Wallet") - .WithMany("Pockets") - .HasForeignKey("WalletId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_wallet_pockets_wallets_wallet_id"); - - b.Navigation("Wallet"); - }); - - modelBuilder.Entity("PostPostCategory", b => - { - b.HasOne("DysonNetwork.Sphere.Post.PostCategory", null) - .WithMany() - .HasForeignKey("CategoriesId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_category_links_post_categories_categories_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany() - .HasForeignKey("PostsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_category_links_posts_posts_id"); - }); - - modelBuilder.Entity("PostPostCollection", b => - { - b.HasOne("DysonNetwork.Sphere.Post.PostCollection", null) - .WithMany() - .HasForeignKey("CollectionsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_collection_links_post_collections_collections_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany() - .HasForeignKey("PostsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_collection_links_posts_posts_id"); - }); - - modelBuilder.Entity("PostPostTag", b => - { - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany() - .HasForeignKey("PostsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_tag_links_posts_posts_id"); - - b.HasOne("DysonNetwork.Sphere.Post.PostTag", null) - .WithMany() - .HasForeignKey("TagsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_tag_links_post_tags_tags_id"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Account", 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"); - - b.Navigation("Subscriptions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatRoom", b => - { - b.Navigation("Members"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.Message", b => - { - b.Navigation("OutdatedAttachments"); - - b.Navigation("Reactions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Connection.WebReader.WebFeed", b => - { - b.Navigation("Articles"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomApp", b => - { - b.Navigation("Secrets"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroup", b => - { - b.Navigation("Members"); - - b.Navigation("Nodes"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.Post", b => - { - b.Navigation("OutdatedAttachments"); - - b.Navigation("Reactions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.Publisher", b => - { - b.Navigation("Collections"); - - b.Navigation("Features"); - - b.Navigation("Members"); - - b.Navigation("Posts"); - - b.Navigation("Subscriptions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.Realm", b => - { - b.Navigation("ChatRooms"); - - b.Navigation("Members"); - - b.Navigation("RealmTags"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.Tag", b => - { - b.Navigation("RealmTags"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Wallet", b => - { - b.Navigation("Pockets"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/DysonNetwork.Sphere/Data/Migrations/20250629123136_CustomAppsRefine.cs b/DysonNetwork.Sphere/Data/Migrations/20250629123136_CustomAppsRefine.cs deleted file mode 100644 index ff6d5ad..0000000 --- a/DysonNetwork.Sphere/Data/Migrations/20250629123136_CustomAppsRefine.cs +++ /dev/null @@ -1,182 +0,0 @@ -using DysonNetwork.Sphere.Account; -using DysonNetwork.Sphere.Developer; -using DysonNetwork.Sphere.Storage; -using Microsoft.EntityFrameworkCore.Migrations; -using NodaTime; - -#nullable disable - -namespace DysonNetwork.Sphere.Data.Migrations -{ - /// - public partial class CustomAppsRefine : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "allow_offline_access", - table: "custom_apps"); - - migrationBuilder.DropColumn( - name: "allowed_grant_types", - table: "custom_apps"); - - migrationBuilder.DropColumn( - name: "allowed_scopes", - table: "custom_apps"); - - migrationBuilder.DropColumn( - name: "client_uri", - table: "custom_apps"); - - migrationBuilder.DropColumn( - name: "logo_uri", - table: "custom_apps"); - - migrationBuilder.DropColumn( - name: "post_logout_redirect_uris", - table: "custom_apps"); - - migrationBuilder.DropColumn( - name: "redirect_uris", - table: "custom_apps"); - - migrationBuilder.DropColumn( - name: "require_pkce", - table: "custom_apps"); - - migrationBuilder.DropColumn( - name: "verified_at", - table: "custom_apps"); - - migrationBuilder.RenameColumn( - name: "verified_as", - table: "custom_apps", - newName: "description"); - - migrationBuilder.AddColumn( - name: "background", - table: "custom_apps", - type: "jsonb", - nullable: true); - - migrationBuilder.AddColumn( - name: "links", - table: "custom_apps", - type: "jsonb", - nullable: true); - - migrationBuilder.AddColumn( - name: "oauth_config", - table: "custom_apps", - type: "jsonb", - nullable: true); - - migrationBuilder.AddColumn( - name: "picture", - table: "custom_apps", - type: "jsonb", - nullable: true); - - migrationBuilder.AddColumn( - name: "verification", - table: "custom_apps", - type: "jsonb", - nullable: true); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "background", - table: "custom_apps"); - - migrationBuilder.DropColumn( - name: "links", - table: "custom_apps"); - - migrationBuilder.DropColumn( - name: "oauth_config", - table: "custom_apps"); - - migrationBuilder.DropColumn( - name: "picture", - table: "custom_apps"); - - migrationBuilder.DropColumn( - name: "verification", - table: "custom_apps"); - - migrationBuilder.RenameColumn( - name: "description", - table: "custom_apps", - newName: "verified_as"); - - migrationBuilder.AddColumn( - name: "allow_offline_access", - table: "custom_apps", - type: "boolean", - nullable: false, - defaultValue: false); - - migrationBuilder.AddColumn( - name: "allowed_grant_types", - table: "custom_apps", - type: "character varying(256)", - maxLength: 256, - nullable: false, - defaultValue: ""); - - migrationBuilder.AddColumn( - name: "allowed_scopes", - table: "custom_apps", - type: "character varying(256)", - maxLength: 256, - nullable: true); - - migrationBuilder.AddColumn( - name: "client_uri", - table: "custom_apps", - type: "character varying(1024)", - maxLength: 1024, - nullable: true); - - migrationBuilder.AddColumn( - name: "logo_uri", - table: "custom_apps", - type: "character varying(4096)", - maxLength: 4096, - nullable: true); - - migrationBuilder.AddColumn( - name: "post_logout_redirect_uris", - table: "custom_apps", - type: "character varying(4096)", - maxLength: 4096, - nullable: true); - - migrationBuilder.AddColumn( - name: "redirect_uris", - table: "custom_apps", - type: "character varying(4096)", - maxLength: 4096, - nullable: false, - defaultValue: ""); - - migrationBuilder.AddColumn( - name: "require_pkce", - table: "custom_apps", - type: "boolean", - nullable: false, - defaultValue: false); - - migrationBuilder.AddColumn( - name: "verified_at", - table: "custom_apps", - type: "timestamp with time zone", - nullable: true); - } - } -} diff --git a/DysonNetwork.Sphere/Developer/CustomApp.cs b/DysonNetwork.Sphere/Developer/CustomApp.cs index d1aaaeb..0d3ba5d 100644 --- a/DysonNetwork.Sphere/Developer/CustomApp.cs +++ b/DysonNetwork.Sphere/Developer/CustomApp.cs @@ -35,7 +35,7 @@ public class CustomApp : ModelBase, IIdentifiedResource public Guid PublisherId { get; set; } public Publisher.Publisher Developer { get; set; } = null!; - [NotMapped] public string ResourceIdentifier => "custom-app/" + Id; + [NotMapped] public string ResourceIdentifier => "custom-app:" + Id; } public class CustomAppLinks diff --git a/DysonNetwork.Sphere/Developer/CustomAppController.cs b/DysonNetwork.Sphere/Developer/CustomAppController.cs index 4b354d4..c0e42ec 100644 --- a/DysonNetwork.Sphere/Developer/CustomAppController.cs +++ b/DysonNetwork.Sphere/Developer/CustomAppController.cs @@ -47,7 +47,7 @@ public class CustomAppController(CustomAppService customApps, PublisherService p [Authorize] public async Task CreateApp([FromRoute] string pubName, [FromBody] CustomAppRequest request) { - if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); if (string.IsNullOrWhiteSpace(request.Name) || string.IsNullOrWhiteSpace(request.Slug)) return BadRequest("Name and slug are required"); @@ -79,7 +79,7 @@ public class CustomAppController(CustomAppService customApps, PublisherService p [FromBody] CustomAppRequest request ) { - if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); var publisher = await ps.GetPublisherByName(pubName); if (publisher is null) return NotFound(); @@ -109,7 +109,7 @@ public class CustomAppController(CustomAppService customApps, PublisherService p [FromRoute] Guid id ) { - if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); var publisher = await ps.GetPublisherByName(pubName); if (publisher is null) return NotFound(); diff --git a/DysonNetwork.Sphere/Developer/DeveloperController.cs b/DysonNetwork.Sphere/Developer/DeveloperController.cs index 8408141..4981bf6 100644 --- a/DysonNetwork.Sphere/Developer/DeveloperController.cs +++ b/DysonNetwork.Sphere/Developer/DeveloperController.cs @@ -63,7 +63,7 @@ public class DeveloperController( [Authorize] public async Task>> ListJoinedDevelopers() { - if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); var userId = currentUser.Id; var members = await db.PublisherMembers @@ -93,7 +93,7 @@ public class DeveloperController( [RequiredPermission("global", "developers.create")] public async Task> EnrollDeveloperProgram(string name) { - if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); var userId = currentUser.Id; var publisher = await db.Publishers diff --git a/DysonNetwork.Sphere/DysonNetwork.Sphere.csproj b/DysonNetwork.Sphere/DysonNetwork.Sphere.csproj index ca9d807..3ddb288 100644 --- a/DysonNetwork.Sphere/DysonNetwork.Sphere.csproj +++ b/DysonNetwork.Sphere/DysonNetwork.Sphere.csproj @@ -85,7 +85,7 @@ - + @@ -164,6 +164,19 @@ <_ContentIncludedByDefault Remove="app\publish\Keys\Solian.json" /> <_ContentIncludedByDefault Remove="app\publish\package-lock.json" /> <_ContentIncludedByDefault Remove="app\publish\package.json" /> + <_ContentIncludedByDefault Remove="Pages\Account\Profile.cshtml" /> + <_ContentIncludedByDefault Remove="Pages\Auth\Authorize.cshtml" /> + <_ContentIncludedByDefault Remove="Pages\Auth\Callback.cshtml" /> + <_ContentIncludedByDefault Remove="Pages\Auth\Challenge.cshtml" /> + <_ContentIncludedByDefault Remove="Pages\Auth\Login.cshtml" /> + <_ContentIncludedByDefault Remove="Pages\Auth\SelectFactor.cshtml" /> + <_ContentIncludedByDefault Remove="Pages\Auth\VerifyFactor.cshtml" /> + <_ContentIncludedByDefault Remove="Pages\Checkpoint\CheckpointPage.cshtml" /> + <_ContentIncludedByDefault Remove="Pages\Spell\MagicSpellPage.cshtml" /> + + + + diff --git a/DysonNetwork.Sphere/Email/EmailModels.cs b/DysonNetwork.Sphere/Email/EmailModels.cs deleted file mode 100644 index 1a53142..0000000 --- a/DysonNetwork.Sphere/Email/EmailModels.cs +++ /dev/null @@ -1,31 +0,0 @@ -namespace DysonNetwork.Sphere.Email; - -public class LandingEmailModel -{ - public required string Name { get; set; } - public required string Link { get; set; } -} - -public class AccountDeletionEmailModel -{ - public required string Name { get; set; } - public required string Link { get; set; } -} - -public class PasswordResetEmailModel -{ - public required string Name { get; set; } - public required string Link { get; set; } -} - -public class VerificationEmailModel -{ - public required string Name { get; set; } - public required string Code { get; set; } -} - -public class ContactVerificationEmailModel -{ - public required string Name { get; set; } - public required string Link { get; set; } -} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Email/EmailService.cs b/DysonNetwork.Sphere/Email/EmailService.cs deleted file mode 100644 index f533e57..0000000 --- a/DysonNetwork.Sphere/Email/EmailService.cs +++ /dev/null @@ -1,106 +0,0 @@ -using MailKit.Net.Smtp; -using Microsoft.AspNetCore.Components; -using MimeKit; - -namespace DysonNetwork.Sphere.Email; - -public class EmailServiceConfiguration -{ - public string Server { get; set; } = null!; - public int Port { get; set; } - public bool UseSsl { get; set; } - public string Username { get; set; } = null!; - public string Password { get; set; } = null!; - public string FromAddress { get; set; } = null!; - public string FromName { get; set; } = null!; - public string SubjectPrefix { get; set; } = null!; -} - -public class EmailService -{ - private readonly EmailServiceConfiguration _configuration; - private readonly RazorViewRenderer _viewRenderer; - private readonly ILogger _logger; - - public EmailService(IConfiguration configuration, RazorViewRenderer viewRenderer, ILogger logger) - { - var cfg = configuration.GetSection("Email").Get(); - _configuration = cfg ?? throw new ArgumentException("Email service was not configured."); - _viewRenderer = viewRenderer; - _logger = logger; - } - - public async Task SendEmailAsync(string? recipientName, string recipientEmail, string subject, string textBody) - { - await SendEmailAsync(recipientName, recipientEmail, subject, textBody, null); - } - - public async Task SendEmailAsync(string? recipientName, string recipientEmail, string subject, string textBody, - string? htmlBody) - { - subject = $"[{_configuration.SubjectPrefix}] {subject}"; - - var emailMessage = new MimeMessage(); - emailMessage.From.Add(new MailboxAddress(_configuration.FromName, _configuration.FromAddress)); - emailMessage.To.Add(new MailboxAddress(recipientName, recipientEmail)); - emailMessage.Subject = subject; - - var bodyBuilder = new BodyBuilder - { - TextBody = textBody - }; - - if (!string.IsNullOrEmpty(htmlBody)) - bodyBuilder.HtmlBody = htmlBody; - - emailMessage.Body = bodyBuilder.ToMessageBody(); - - using var client = new SmtpClient(); - await client.ConnectAsync(_configuration.Server, _configuration.Port, _configuration.UseSsl); - await client.AuthenticateAsync(_configuration.Username, _configuration.Password); - await client.SendAsync(emailMessage); - await client.DisconnectAsync(true); - } - - private static string _ConvertHtmlToPlainText(string html) - { - // Remove style tags and their contents - html = System.Text.RegularExpressions.Regex.Replace(html, "]*>.*?", "", - System.Text.RegularExpressions.RegexOptions.Singleline); - - // Replace header tags with text + newlines - html = System.Text.RegularExpressions.Regex.Replace(html, "]*>(.*?)", "$1\n\n", - System.Text.RegularExpressions.RegexOptions.IgnoreCase); - - // Replace line breaks - html = html.Replace("
", "\n").Replace("
", "\n").Replace("
", "\n"); - - // Remove all remaining HTML tags - html = System.Text.RegularExpressions.Regex.Replace(html, "<[^>]+>", ""); - - // Decode HTML entities - html = System.Net.WebUtility.HtmlDecode(html); - - // Remove excess whitespace - html = System.Text.RegularExpressions.Regex.Replace(html, @"\s+", " ").Trim(); - - return html; - } - - public async Task SendTemplatedEmailAsync(string? recipientName, string recipientEmail, - string subject, TModel model) - where TComponent : IComponent - { - try - { - var htmlBody = await _viewRenderer.RenderComponentToStringAsync(model); - var fallbackTextBody = _ConvertHtmlToPlainText(htmlBody); - await SendEmailAsync(recipientName, recipientEmail, subject, fallbackTextBody, htmlBody); - } - catch (Exception err) - { - _logger.LogError(err, "Failed to render email template..."); - throw; - } - } -} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Email/RazorViewRenderer.cs b/DysonNetwork.Sphere/Email/RazorViewRenderer.cs deleted file mode 100644 index 72331f9..0000000 --- a/DysonNetwork.Sphere/Email/RazorViewRenderer.cs +++ /dev/null @@ -1,45 +0,0 @@ -using Microsoft.AspNetCore.Components; -using Microsoft.AspNetCore.Components.Web; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.Abstractions; -using Microsoft.AspNetCore.Mvc.ModelBinding; -using Microsoft.AspNetCore.Mvc.Razor; -using Microsoft.AspNetCore.Mvc.Rendering; -using Microsoft.AspNetCore.Mvc.ViewEngines; -using Microsoft.AspNetCore.Mvc.ViewFeatures; -using RouteData = Microsoft.AspNetCore.Routing.RouteData; - -namespace DysonNetwork.Sphere.Email; - -public class RazorViewRenderer( - IServiceProvider serviceProvider, - ILoggerFactory loggerFactory, - ILogger logger -) -{ - public async Task RenderComponentToStringAsync(TModel? model) - where TComponent : IComponent - { - await using var htmlRenderer = new HtmlRenderer(serviceProvider, loggerFactory); - - return await htmlRenderer.Dispatcher.InvokeAsync(async () => - { - try - { - var dictionary = model?.GetType().GetProperties() - .ToDictionary( - prop => prop.Name, - prop => prop.GetValue(model, null) - ) ?? new Dictionary(); - var parameterView = ParameterView.FromDictionary(dictionary); - var output = await htmlRenderer.RenderComponentAsync(parameterView); - return output.ToHtmlString(); - } - catch (Exception ex) - { - logger.LogError(ex, "Error rendering component {ComponentName}", typeof(TComponent).Name); - throw; - } - }); - } -} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Migrations/20250520160525_InitialMigration.Designer.cs b/DysonNetwork.Sphere/Migrations/20250520160525_InitialMigration.Designer.cs deleted file mode 100644 index 9f0f5ba..0000000 --- a/DysonNetwork.Sphere/Migrations/20250520160525_InitialMigration.Designer.cs +++ /dev/null @@ -1,3426 +0,0 @@ -// -using System; -using System.Collections.Generic; -using System.Text.Json; -using DysonNetwork.Sphere; -using DysonNetwork.Sphere.Account; -using DysonNetwork.Sphere.Storage; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using NetTopologySuite.Geometries; -using NodaTime; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; -using NpgsqlTypes; - -#nullable disable - -namespace DysonNetwork.Sphere.Migrations -{ - [DbContext(typeof(AppDatabase))] - [Migration("20250520160525_InitialMigration")] - partial class InitialMigration - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "9.0.3") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "postgis"); - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Account", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("ActivatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("activated_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("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("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.Sphere.Account.AccountAuthFactor", 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("Secret") - .HasMaxLength(8196) - .HasColumnType("character varying(8196)") - .HasColumnName("secret"); - - 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.Sphere.Account.AccountContact", 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("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.Sphere.Account.ActionLog", 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("geometry") - .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.HasIndex("SessionId") - .HasDatabaseName("ix_action_logs_session_id"); - - b.ToTable("action_logs", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Badge", 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("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.Sphere.Account.CheckInResult", 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("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.Sphere.Account.MagicSpell", 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.Sphere.Account.Notification", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("Content") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .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>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("Priority") - .HasColumnType("integer") - .HasColumnName("priority"); - - b.Property("Subtitle") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("subtitle"); - - b.Property("Title") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("title"); - - b.Property("Topic") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("topic"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("ViewedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("viewed_at"); - - b.HasKey("Id") - .HasName("pk_notifications"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_notifications_account_id"); - - b.ToTable("notifications", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.NotificationPushSubscription", 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(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("device_id"); - - b.Property("DeviceToken") - .IsRequired() - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("device_token"); - - b.Property("LastUsedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_used_at"); - - b.Property("Provider") - .HasColumnType("integer") - .HasColumnName("provider"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_notification_push_subscriptions"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_notification_push_subscriptions_account_id"); - - b.HasIndex("DeviceId") - .IsUnique() - .HasDatabaseName("ix_notification_push_subscriptions_device_id"); - - b.HasIndex("DeviceToken") - .IsUnique() - .HasDatabaseName("ix_notification_push_subscriptions_device_token"); - - b.ToTable("notification_push_subscriptions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Profile", b => - { - b.Property("Id") - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_id"); - - b.Property("Bio") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("bio"); - - 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("LastName") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("last_name"); - - b.Property("MiddleName") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("middle_name"); - - b.Property("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_account_profiles"); - - b.HasIndex("BackgroundId") - .HasDatabaseName("ix_account_profiles_background_id"); - - b.HasIndex("PictureId") - .HasDatabaseName("ix_account_profiles_picture_id"); - - b.ToTable("account_profiles", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Relationship", 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("integer") - .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.Sphere.Account.Status", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - 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("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("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.Sphere.Activity.Activity", 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>("Meta") - .IsRequired() - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("ResourceIdentifier") - .IsRequired() - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("resource_identifier"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property>("UsersVisible") - .IsRequired() - .HasColumnType("jsonb") - .HasColumnName("users_visible"); - - b.Property("Visibility") - .HasColumnType("integer") - .HasColumnName("visibility"); - - b.HasKey("Id") - .HasName("pk_activities"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_activities_account_id"); - - b.ToTable("activities", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Challenge", 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") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("device_id"); - - 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("geometry") - .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("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_challenges"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_auth_challenges_account_id"); - - b.ToTable("auth_challenges", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Session", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("ChallengeId") - .HasColumnType("uuid") - .HasColumnName("challenge_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("Label") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("label"); - - b.Property("LastGrantedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_granted_at"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - 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.ToTable("auth_sessions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatMember", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("ChatRoomId") - .HasColumnType("uuid") - .HasColumnName("chat_room_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("IsBot") - .HasColumnType("boolean") - .HasColumnName("is_bot"); - - b.Property("JoinedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("joined_at"); - - b.Property("LeaveAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("leave_at"); - - b.Property("Nick") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("nick"); - - b.Property("Notify") - .HasColumnType("integer") - .HasColumnName("notify"); - - b.Property("Role") - .HasColumnType("integer") - .HasColumnName("role"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_chat_members"); - - b.HasAlternateKey("ChatRoomId", "AccountId") - .HasName("ak_chat_members_chat_room_id_account_id"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_chat_members_account_id"); - - b.ToTable("chat_members", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatRoom", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_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("Description") - .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") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - b.Property("RealmId") - .HasColumnType("uuid") - .HasColumnName("realm_id"); - - b.Property("Type") - .HasColumnType("integer") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_chat_rooms"); - - b.HasIndex("BackgroundId") - .HasDatabaseName("ix_chat_rooms_background_id"); - - b.HasIndex("PictureId") - .HasDatabaseName("ix_chat_rooms_picture_id"); - - b.HasIndex("RealmId") - .HasDatabaseName("ix_chat_rooms_realm_id"); - - b.ToTable("chat_rooms", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.Message", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("ChatRoomId") - .HasColumnType("uuid") - .HasColumnName("chat_room_id"); - - b.Property("Content") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .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("EditedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("edited_at"); - - b.Property("ForwardedMessageId") - .HasColumnType("uuid") - .HasColumnName("forwarded_message_id"); - - b.Property>("MembersMentioned") - .HasColumnType("jsonb") - .HasColumnName("members_mentioned"); - - b.Property>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("Nonce") - .IsRequired() - .HasMaxLength(36) - .HasColumnType("character varying(36)") - .HasColumnName("nonce"); - - b.Property("RepliedMessageId") - .HasColumnType("uuid") - .HasColumnName("replied_message_id"); - - b.Property("SenderId") - .HasColumnType("uuid") - .HasColumnName("sender_id"); - - b.Property("Type") - .IsRequired() - .HasColumnType("text") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_chat_messages"); - - b.HasIndex("ChatRoomId") - .HasDatabaseName("ix_chat_messages_chat_room_id"); - - b.HasIndex("ForwardedMessageId") - .HasDatabaseName("ix_chat_messages_forwarded_message_id"); - - b.HasIndex("RepliedMessageId") - .HasDatabaseName("ix_chat_messages_replied_message_id"); - - b.HasIndex("SenderId") - .HasDatabaseName("ix_chat_messages_sender_id"); - - b.ToTable("chat_messages", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.MessageReaction", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("Attitude") - .HasColumnType("integer") - .HasColumnName("attitude"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("MessageId") - .HasColumnType("uuid") - .HasColumnName("message_id"); - - b.Property("SenderId") - .HasColumnType("uuid") - .HasColumnName("sender_id"); - - b.Property("Symbol") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("symbol"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_chat_reactions"); - - b.HasIndex("MessageId") - .HasDatabaseName("ix_chat_reactions_message_id"); - - b.HasIndex("SenderId") - .HasDatabaseName("ix_chat_reactions_sender_id"); - - b.ToTable("chat_reactions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.MessageReadReceipt", b => - { - b.Property("MessageId") - .HasColumnType("uuid") - .HasColumnName("message_id"); - - b.Property("SenderId") - .HasColumnType("uuid") - .HasColumnName("sender_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("MessageId", "SenderId") - .HasName("pk_chat_read_receipts"); - - b.HasIndex("SenderId") - .HasDatabaseName("ix_chat_read_receipts_sender_id"); - - b.HasIndex("MessageId", "SenderId") - .IsUnique() - .HasDatabaseName("ix_chat_read_receipts_message_id_sender_id"); - - b.ToTable("chat_read_receipts", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.RealtimeCall", 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("EndedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("ended_at"); - - b.Property("RoomId") - .HasColumnType("uuid") - .HasColumnName("room_id"); - - b.Property("SenderId") - .HasColumnType("uuid") - .HasColumnName("sender_id"); - - b.Property("Title") - .HasColumnType("text") - .HasColumnName("title"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_chat_realtime_call"); - - b.HasIndex("RoomId") - .HasDatabaseName("ix_chat_realtime_call_room_id"); - - b.HasIndex("SenderId") - .HasDatabaseName("ix_chat_realtime_call_sender_id"); - - b.ToTable("chat_realtime_call", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomApp", 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("Name") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("slug"); - - b.Property("Status") - .HasColumnType("integer") - .HasColumnName("status"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("VerifiedAs") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("verified_as"); - - b.Property("VerifiedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("verified_at"); - - b.HasKey("Id") - .HasName("pk_custom_apps"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_custom_apps_publisher_id"); - - b.ToTable("custom_apps", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomAppSecret", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AppId") - .HasColumnType("uuid") - .HasColumnName("app_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("Remarks") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("remarks"); - - b.Property("Secret") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("secret"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_custom_app_secrets"); - - b.HasIndex("AppId") - .HasDatabaseName("ix_custom_app_secrets_app_id"); - - b.ToTable("custom_app_secrets", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroup", 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.Sphere.Permission.PermissionGroupMember", 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.Sphere.Permission.PermissionNode", 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("Area") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("area"); - - 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("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", "Area", "Actor") - .HasDatabaseName("ix_permission_nodes_key_area_actor"); - - b.ToTable("permission_nodes", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.Post", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("Content") - .HasColumnType("text") - .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("Description") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("Downvotes") - .HasColumnType("integer") - .HasColumnName("downvotes"); - - b.Property("EditedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("edited_at"); - - b.Property("ForwardedPostId") - .HasColumnType("uuid") - .HasColumnName("forwarded_post_id"); - - b.Property("Language") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("language"); - - b.Property>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("PublishedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("published_at"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("RepliedPostId") - .HasColumnType("uuid") - .HasColumnName("replied_post_id"); - - b.Property("SearchVector") - .IsRequired() - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("tsvector") - .HasColumnName("search_vector") - .HasAnnotation("Npgsql:TsVectorConfig", "simple") - .HasAnnotation("Npgsql:TsVectorProperties", new[] { "Title", "Description", "Content" }); - - b.Property("ThreadedPostId") - .HasColumnType("uuid") - .HasColumnName("threaded_post_id"); - - b.Property("Title") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("title"); - - b.Property("Type") - .HasColumnType("integer") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("Upvotes") - .HasColumnType("integer") - .HasColumnName("upvotes"); - - b.Property("ViewsTotal") - .HasColumnType("integer") - .HasColumnName("views_total"); - - b.Property("ViewsUnique") - .HasColumnType("integer") - .HasColumnName("views_unique"); - - b.Property("Visibility") - .HasColumnType("integer") - .HasColumnName("visibility"); - - b.HasKey("Id") - .HasName("pk_posts"); - - b.HasIndex("ForwardedPostId") - .HasDatabaseName("ix_posts_forwarded_post_id"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_posts_publisher_id"); - - b.HasIndex("RepliedPostId") - .HasDatabaseName("ix_posts_replied_post_id"); - - b.HasIndex("SearchVector") - .HasDatabaseName("ix_posts_search_vector"); - - NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex("SearchVector"), "GIN"); - - b.HasIndex("ThreadedPostId") - .IsUnique() - .HasDatabaseName("ix_posts_threaded_post_id"); - - b.ToTable("posts", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostCategory", 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("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("name"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_categories"); - - b.ToTable("post_categories", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostCollection", 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("Description") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("name"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_collections"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_post_collections_publisher_id"); - - b.ToTable("post_collections", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostReaction", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("Attitude") - .HasColumnType("integer") - .HasColumnName("attitude"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("PostId") - .HasColumnType("uuid") - .HasColumnName("post_id"); - - b.Property("Symbol") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("symbol"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_reactions"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_post_reactions_account_id"); - - b.HasIndex("PostId") - .HasDatabaseName("ix_post_reactions_post_id"); - - b.ToTable("post_reactions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostTag", 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("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("name"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_tags"); - - b.ToTable("post_tags", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.Publisher", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("BackgroundId") - .HasColumnType("character varying(32)") - .HasColumnName("background_id"); - - b.Property("Bio") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("bio"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - 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("PictureId") - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - b.Property("RealmId") - .HasColumnType("uuid") - .HasColumnName("realm_id"); - - b.Property("Type") - .HasColumnType("integer") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_publishers"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_publishers_account_id"); - - b.HasIndex("BackgroundId") - .HasDatabaseName("ix_publishers_background_id"); - - b.HasIndex("Name") - .IsUnique() - .HasDatabaseName("ix_publishers_name"); - - b.HasIndex("PictureId") - .HasDatabaseName("ix_publishers_picture_id"); - - b.HasIndex("RealmId") - .HasDatabaseName("ix_publishers_realm_id"); - - b.ToTable("publishers", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherFeature", 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("ExpiredAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expired_at"); - - b.Property("Flag") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("flag"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_publisher_features"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_publisher_features_publisher_id"); - - b.ToTable("publisher_features", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherMember", b => - { - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_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("Role") - .HasColumnType("integer") - .HasColumnName("role"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("PublisherId", "AccountId") - .HasName("pk_publisher_members"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_publisher_members_account_id"); - - b.ToTable("publisher_members", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherSubscription", 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("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("Status") - .HasColumnType("integer") - .HasColumnName("status"); - - b.Property("Tier") - .HasColumnType("integer") - .HasColumnName("tier"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_publisher_subscriptions"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_publisher_subscriptions_account_id"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_publisher_subscriptions_publisher_id"); - - b.ToTable("publisher_subscriptions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.Realm", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_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("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("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - 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("VerifiedAs") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("verified_as"); - - b.Property("VerifiedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("verified_at"); - - b.HasKey("Id") - .HasName("pk_realms"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_realms_account_id"); - - b.HasIndex("BackgroundId") - .HasDatabaseName("ix_realms_background_id"); - - b.HasIndex("PictureId") - .HasDatabaseName("ix_realms_picture_id"); - - b.HasIndex("Slug") - .IsUnique() - .HasDatabaseName("ix_realms_slug"); - - b.ToTable("realms", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.RealmMember", 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.HasIndex("AccountId") - .HasDatabaseName("ix_realm_members_account_id"); - - b.ToTable("realm_members", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.Sticker", 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("ImageId") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("image_id"); - - b.Property("PackId") - .HasColumnType("uuid") - .HasColumnName("pack_id"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_stickers"); - - b.HasIndex("ImageId") - .HasDatabaseName("ix_stickers_image_id"); - - b.HasIndex("PackId") - .HasDatabaseName("ix_stickers_pack_id"); - - b.HasIndex("Slug") - .HasDatabaseName("ix_stickers_slug"); - - b.ToTable("stickers", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.StickerPack", 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("Description") - .IsRequired() - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("Prefix") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("prefix"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_sticker_packs"); - - b.HasIndex("Prefix") - .IsUnique() - .HasDatabaseName("ix_sticker_packs_prefix"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_sticker_packs_publisher_id"); - - b.ToTable("sticker_packs", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFile", b => - { - b.Property("Id") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .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("Description") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("ExpiredAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expired_at"); - - b.Property>("FileMeta") - .HasColumnType("jsonb") - .HasColumnName("file_meta"); - - b.Property("HasCompression") - .HasColumnType("boolean") - .HasColumnName("has_compression"); - - b.Property("Hash") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("hash"); - - b.Property("MessageId") - .HasColumnType("uuid") - .HasColumnName("message_id"); - - b.Property("MimeType") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("mime_type"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("PostId") - .HasColumnType("uuid") - .HasColumnName("post_id"); - - b.Property>("SensitiveMarks") - .HasColumnType("jsonb") - .HasColumnName("sensitive_marks"); - - b.Property("Size") - .HasColumnType("bigint") - .HasColumnName("size"); - - b.Property("StorageId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("storage_id"); - - b.Property("StorageUrl") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("storage_url"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("UploadedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("uploaded_at"); - - b.Property("UploadedTo") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("uploaded_to"); - - b.Property("UsedCount") - .HasColumnType("integer") - .HasColumnName("used_count"); - - b.Property>("UserMeta") - .HasColumnType("jsonb") - .HasColumnName("user_meta"); - - b.HasKey("Id") - .HasName("pk_files"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_files_account_id"); - - b.HasIndex("MessageId") - .HasDatabaseName("ix_files_message_id"); - - b.HasIndex("PostId") - .HasDatabaseName("ix_files_post_id"); - - b.ToTable("files", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Order", 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("ExpiredAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expired_at"); - - b.Property("IssuerAppId") - .HasColumnType("uuid") - .HasColumnName("issuer_app_id"); - - b.Property("PayeeWalletId") - .HasColumnType("uuid") - .HasColumnName("payee_wallet_id"); - - 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("IssuerAppId") - .HasDatabaseName("ix_payment_orders_issuer_app_id"); - - 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.Sphere.Wallet.Transaction", 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.Sphere.Wallet.Wallet", 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.Sphere.Wallet.WalletPocket", 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("PostPostCategory", b => - { - b.Property("CategoriesId") - .HasColumnType("uuid") - .HasColumnName("categories_id"); - - b.Property("PostsId") - .HasColumnType("uuid") - .HasColumnName("posts_id"); - - b.HasKey("CategoriesId", "PostsId") - .HasName("pk_post_category_links"); - - b.HasIndex("PostsId") - .HasDatabaseName("ix_post_category_links_posts_id"); - - b.ToTable("post_category_links", (string)null); - }); - - modelBuilder.Entity("PostPostCollection", b => - { - b.Property("CollectionsId") - .HasColumnType("uuid") - .HasColumnName("collections_id"); - - b.Property("PostsId") - .HasColumnType("uuid") - .HasColumnName("posts_id"); - - b.HasKey("CollectionsId", "PostsId") - .HasName("pk_post_collection_links"); - - b.HasIndex("PostsId") - .HasDatabaseName("ix_post_collection_links_posts_id"); - - b.ToTable("post_collection_links", (string)null); - }); - - modelBuilder.Entity("PostPostTag", b => - { - b.Property("PostsId") - .HasColumnType("uuid") - .HasColumnName("posts_id"); - - b.Property("TagsId") - .HasColumnType("uuid") - .HasColumnName("tags_id"); - - b.HasKey("PostsId", "TagsId") - .HasName("pk_post_tag_links"); - - b.HasIndex("TagsId") - .HasDatabaseName("ix_post_tag_links_tags_id"); - - b.ToTable("post_tag_links", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.AccountAuthFactor", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("AuthFactors") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_auth_factors_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.AccountContact", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Contacts") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_contacts_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.ActionLog", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_action_logs_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Auth.Session", "Session") - .WithMany() - .HasForeignKey("SessionId") - .HasConstraintName("fk_action_logs_auth_sessions_session_id"); - - b.Navigation("Account"); - - b.Navigation("Session"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Badge", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Badges") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_badges_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.CheckInResult", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_check_in_results_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.MagicSpell", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .HasConstraintName("fk_magic_spells_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Notification", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_notifications_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.NotificationPushSubscription", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_notification_push_subscriptions_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Profile", b => - { - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "Background") - .WithMany() - .HasForeignKey("BackgroundId") - .HasConstraintName("fk_account_profiles_files_background_id"); - - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithOne("Profile") - .HasForeignKey("DysonNetwork.Sphere.Account.Profile", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_profiles_accounts_id"); - - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "Picture") - .WithMany() - .HasForeignKey("PictureId") - .HasConstraintName("fk_account_profiles_files_picture_id"); - - b.Navigation("Account"); - - b.Navigation("Background"); - - b.Navigation("Picture"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Relationship", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("OutgoingRelationships") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_relationships_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Account.Account", "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.Sphere.Account.Status", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_statuses_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Activity.Activity", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_activities_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Challenge", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Challenges") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_challenges_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Session", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Sessions") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_sessions_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Auth.Challenge", "Challenge") - .WithMany() - .HasForeignKey("ChallengeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_sessions_auth_challenges_challenge_id"); - - b.Navigation("Account"); - - b.Navigation("Challenge"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatMember", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_members_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatRoom", "ChatRoom") - .WithMany("Members") - .HasForeignKey("ChatRoomId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_members_chat_rooms_chat_room_id"); - - b.Navigation("Account"); - - b.Navigation("ChatRoom"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatRoom", b => - { - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "Background") - .WithMany() - .HasForeignKey("BackgroundId") - .HasConstraintName("fk_chat_rooms_files_background_id"); - - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "Picture") - .WithMany() - .HasForeignKey("PictureId") - .HasConstraintName("fk_chat_rooms_files_picture_id"); - - b.HasOne("DysonNetwork.Sphere.Realm.Realm", "Realm") - .WithMany("ChatRooms") - .HasForeignKey("RealmId") - .HasConstraintName("fk_chat_rooms_realms_realm_id"); - - b.Navigation("Background"); - - b.Navigation("Picture"); - - b.Navigation("Realm"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.Message", b => - { - b.HasOne("DysonNetwork.Sphere.Chat.ChatRoom", "ChatRoom") - .WithMany() - .HasForeignKey("ChatRoomId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_messages_chat_rooms_chat_room_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.Message", "ForwardedMessage") - .WithMany() - .HasForeignKey("ForwardedMessageId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_chat_messages_chat_messages_forwarded_message_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.Message", "RepliedMessage") - .WithMany() - .HasForeignKey("RepliedMessageId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_chat_messages_chat_messages_replied_message_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatMember", "Sender") - .WithMany() - .HasForeignKey("SenderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_messages_chat_members_sender_id"); - - b.Navigation("ChatRoom"); - - b.Navigation("ForwardedMessage"); - - b.Navigation("RepliedMessage"); - - b.Navigation("Sender"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.MessageReaction", b => - { - b.HasOne("DysonNetwork.Sphere.Chat.Message", "Message") - .WithMany("Reactions") - .HasForeignKey("MessageId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_reactions_chat_messages_message_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatMember", "Sender") - .WithMany() - .HasForeignKey("SenderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_reactions_chat_members_sender_id"); - - b.Navigation("Message"); - - b.Navigation("Sender"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.MessageReadReceipt", b => - { - b.HasOne("DysonNetwork.Sphere.Chat.Message", "Message") - .WithMany("Statuses") - .HasForeignKey("MessageId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_read_receipts_chat_messages_message_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatMember", "Sender") - .WithMany() - .HasForeignKey("SenderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_read_receipts_chat_members_sender_id"); - - b.Navigation("Message"); - - b.Navigation("Sender"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.RealtimeCall", b => - { - b.HasOne("DysonNetwork.Sphere.Chat.ChatRoom", "Room") - .WithMany() - .HasForeignKey("RoomId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_realtime_call_chat_rooms_room_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatMember", "Sender") - .WithMany() - .HasForeignKey("SenderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_realtime_call_chat_members_sender_id"); - - b.Navigation("Room"); - - b.Navigation("Sender"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomApp", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Developer") - .WithMany() - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_custom_apps_publishers_publisher_id"); - - b.Navigation("Developer"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomAppSecret", b => - { - b.HasOne("DysonNetwork.Sphere.Developer.CustomApp", "App") - .WithMany() - .HasForeignKey("AppId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_custom_app_secrets_custom_apps_app_id"); - - b.Navigation("App"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroupMember", b => - { - b.HasOne("DysonNetwork.Sphere.Permission.PermissionGroup", "Group") - .WithMany("Members") - .HasForeignKey("GroupId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_permission_group_members_permission_groups_group_id"); - - b.Navigation("Group"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionNode", b => - { - b.HasOne("DysonNetwork.Sphere.Permission.PermissionGroup", "Group") - .WithMany("Nodes") - .HasForeignKey("GroupId") - .HasConstraintName("fk_permission_nodes_permission_groups_group_id"); - - b.Navigation("Group"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.Post", b => - { - b.HasOne("DysonNetwork.Sphere.Post.Post", "ForwardedPost") - .WithMany() - .HasForeignKey("ForwardedPostId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_posts_posts_forwarded_post_id"); - - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Posts") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_posts_publishers_publisher_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", "RepliedPost") - .WithMany() - .HasForeignKey("RepliedPostId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_posts_posts_replied_post_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", "ThreadedPost") - .WithOne() - .HasForeignKey("DysonNetwork.Sphere.Post.Post", "ThreadedPostId") - .HasConstraintName("fk_posts_posts_threaded_post_id"); - - b.Navigation("ForwardedPost"); - - b.Navigation("Publisher"); - - b.Navigation("RepliedPost"); - - b.Navigation("ThreadedPost"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostCollection", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Collections") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_collections_publishers_publisher_id"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostReaction", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_reactions_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", "Post") - .WithMany("Reactions") - .HasForeignKey("PostId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_reactions_posts_post_id"); - - b.Navigation("Account"); - - b.Navigation("Post"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.Publisher", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .HasConstraintName("fk_publishers_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "Background") - .WithMany() - .HasForeignKey("BackgroundId") - .HasConstraintName("fk_publishers_files_background_id"); - - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "Picture") - .WithMany() - .HasForeignKey("PictureId") - .HasConstraintName("fk_publishers_files_picture_id"); - - b.HasOne("DysonNetwork.Sphere.Realm.Realm", "Realm") - .WithMany() - .HasForeignKey("RealmId") - .HasConstraintName("fk_publishers_realms_realm_id"); - - b.Navigation("Account"); - - b.Navigation("Background"); - - b.Navigation("Picture"); - - b.Navigation("Realm"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherFeature", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany() - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_features_publishers_publisher_id"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherMember", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_members_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Members") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_members_publishers_publisher_id"); - - b.Navigation("Account"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherSubscription", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_subscriptions_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Subscriptions") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_subscriptions_publishers_publisher_id"); - - b.Navigation("Account"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.Realm", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_realms_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "Background") - .WithMany() - .HasForeignKey("BackgroundId") - .HasConstraintName("fk_realms_files_background_id"); - - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "Picture") - .WithMany() - .HasForeignKey("PictureId") - .HasConstraintName("fk_realms_files_picture_id"); - - b.Navigation("Account"); - - b.Navigation("Background"); - - b.Navigation("Picture"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.RealmMember", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_realm_members_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Realm.Realm", "Realm") - .WithMany("Members") - .HasForeignKey("RealmId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_realm_members_realms_realm_id"); - - b.Navigation("Account"); - - b.Navigation("Realm"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.Sticker", b => - { - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "Image") - .WithMany() - .HasForeignKey("ImageId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_stickers_files_image_id"); - - b.HasOne("DysonNetwork.Sphere.Sticker.StickerPack", "Pack") - .WithMany() - .HasForeignKey("PackId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_stickers_sticker_packs_pack_id"); - - b.Navigation("Image"); - - b.Navigation("Pack"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.StickerPack", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany() - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_sticker_packs_publishers_publisher_id"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFile", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_files_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.Message", null) - .WithMany("Attachments") - .HasForeignKey("MessageId") - .HasConstraintName("fk_files_chat_messages_message_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany("Attachments") - .HasForeignKey("PostId") - .HasConstraintName("fk_files_posts_post_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Order", b => - { - b.HasOne("DysonNetwork.Sphere.Developer.CustomApp", "IssuerApp") - .WithMany() - .HasForeignKey("IssuerAppId") - .HasConstraintName("fk_payment_orders_custom_apps_issuer_app_id"); - - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "PayeeWallet") - .WithMany() - .HasForeignKey("PayeeWalletId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_payment_orders_wallets_payee_wallet_id"); - - b.HasOne("DysonNetwork.Sphere.Wallet.Transaction", "Transaction") - .WithMany() - .HasForeignKey("TransactionId") - .HasConstraintName("fk_payment_orders_payment_transactions_transaction_id"); - - b.Navigation("IssuerApp"); - - b.Navigation("PayeeWallet"); - - b.Navigation("Transaction"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Transaction", b => - { - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "PayeeWallet") - .WithMany() - .HasForeignKey("PayeeWalletId") - .HasConstraintName("fk_payment_transactions_wallets_payee_wallet_id"); - - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "PayerWallet") - .WithMany() - .HasForeignKey("PayerWalletId") - .HasConstraintName("fk_payment_transactions_wallets_payer_wallet_id"); - - b.Navigation("PayeeWallet"); - - b.Navigation("PayerWallet"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Wallet", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_wallets_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.WalletPocket", b => - { - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "Wallet") - .WithMany("Pockets") - .HasForeignKey("WalletId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_wallet_pockets_wallets_wallet_id"); - - b.Navigation("Wallet"); - }); - - modelBuilder.Entity("PostPostCategory", b => - { - b.HasOne("DysonNetwork.Sphere.Post.PostCategory", null) - .WithMany() - .HasForeignKey("CategoriesId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_category_links_post_categories_categories_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany() - .HasForeignKey("PostsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_category_links_posts_posts_id"); - }); - - modelBuilder.Entity("PostPostCollection", b => - { - b.HasOne("DysonNetwork.Sphere.Post.PostCollection", null) - .WithMany() - .HasForeignKey("CollectionsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_collection_links_post_collections_collections_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany() - .HasForeignKey("PostsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_collection_links_posts_posts_id"); - }); - - modelBuilder.Entity("PostPostTag", b => - { - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany() - .HasForeignKey("PostsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_tag_links_posts_posts_id"); - - b.HasOne("DysonNetwork.Sphere.Post.PostTag", null) - .WithMany() - .HasForeignKey("TagsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_tag_links_post_tags_tags_id"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Account", b => - { - b.Navigation("AuthFactors"); - - b.Navigation("Badges"); - - b.Navigation("Challenges"); - - b.Navigation("Contacts"); - - b.Navigation("IncomingRelationships"); - - b.Navigation("OutgoingRelationships"); - - b.Navigation("Profile") - .IsRequired(); - - b.Navigation("Sessions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatRoom", b => - { - b.Navigation("Members"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.Message", b => - { - b.Navigation("Attachments"); - - b.Navigation("Reactions"); - - b.Navigation("Statuses"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroup", b => - { - b.Navigation("Members"); - - b.Navigation("Nodes"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.Post", b => - { - b.Navigation("Attachments"); - - b.Navigation("Reactions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.Publisher", b => - { - b.Navigation("Collections"); - - b.Navigation("Members"); - - b.Navigation("Posts"); - - b.Navigation("Subscriptions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.Realm", b => - { - b.Navigation("ChatRooms"); - - b.Navigation("Members"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Wallet", b => - { - b.Navigation("Pockets"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/DysonNetwork.Sphere/Migrations/20250520160525_InitialMigration.cs b/DysonNetwork.Sphere/Migrations/20250520160525_InitialMigration.cs deleted file mode 100644 index 5b82f9a..0000000 --- a/DysonNetwork.Sphere/Migrations/20250520160525_InitialMigration.cs +++ /dev/null @@ -1,1992 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text.Json; -using DysonNetwork.Sphere.Account; -using DysonNetwork.Sphere.Storage; -using Microsoft.EntityFrameworkCore.Migrations; -using NetTopologySuite.Geometries; -using NodaTime; -using NpgsqlTypes; - -#nullable disable - -namespace DysonNetwork.Sphere.Migrations -{ - /// - public partial class InitialMigration : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AlterDatabase() - .Annotation("Npgsql:PostgresExtension:postgis", ",,"); - - migrationBuilder.CreateTable( - name: "accounts", - columns: table => new - { - id = table.Column(type: "uuid", nullable: false), - name = table.Column(type: "character varying(256)", maxLength: 256, nullable: false), - nick = table.Column(type: "character varying(256)", maxLength: 256, nullable: false), - language = table.Column(type: "character varying(32)", maxLength: 32, nullable: false), - activated_at = table.Column(type: "timestamp with time zone", nullable: true), - is_superuser = table.Column(type: "boolean", nullable: false), - created_at = table.Column(type: "timestamp with time zone", nullable: false), - updated_at = table.Column(type: "timestamp with time zone", nullable: false), - deleted_at = table.Column(type: "timestamp with time zone", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("pk_accounts", x => x.id); - }); - - migrationBuilder.CreateTable( - name: "permission_groups", - columns: table => new - { - id = table.Column(type: "uuid", nullable: false), - key = table.Column(type: "character varying(1024)", maxLength: 1024, nullable: false), - created_at = table.Column(type: "timestamp with time zone", nullable: false), - updated_at = table.Column(type: "timestamp with time zone", nullable: false), - deleted_at = table.Column(type: "timestamp with time zone", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("pk_permission_groups", x => x.id); - }); - - migrationBuilder.CreateTable( - name: "post_categories", - columns: table => new - { - id = table.Column(type: "uuid", nullable: false), - slug = table.Column(type: "character varying(128)", maxLength: 128, nullable: false), - name = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), - created_at = table.Column(type: "timestamp with time zone", nullable: false), - updated_at = table.Column(type: "timestamp with time zone", nullable: false), - deleted_at = table.Column(type: "timestamp with time zone", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("pk_post_categories", x => x.id); - }); - - migrationBuilder.CreateTable( - name: "post_tags", - columns: table => new - { - id = table.Column(type: "uuid", nullable: false), - slug = table.Column(type: "character varying(128)", maxLength: 128, nullable: false), - name = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), - created_at = table.Column(type: "timestamp with time zone", nullable: false), - updated_at = table.Column(type: "timestamp with time zone", nullable: false), - deleted_at = table.Column(type: "timestamp with time zone", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("pk_post_tags", x => x.id); - }); - - migrationBuilder.CreateTable( - name: "account_auth_factors", - columns: table => new - { - id = table.Column(type: "uuid", nullable: false), - type = table.Column(type: "integer", nullable: false), - secret = table.Column(type: "character varying(8196)", maxLength: 8196, nullable: true), - account_id = table.Column(type: "uuid", nullable: false), - created_at = table.Column(type: "timestamp with time zone", nullable: false), - updated_at = table.Column(type: "timestamp with time zone", nullable: false), - deleted_at = table.Column(type: "timestamp with time zone", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("pk_account_auth_factors", x => x.id); - table.ForeignKey( - name: "fk_account_auth_factors_accounts_account_id", - column: x => x.account_id, - principalTable: "accounts", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "account_check_in_results", - columns: table => new - { - id = table.Column(type: "uuid", nullable: false), - level = table.Column(type: "integer", nullable: false), - reward_points = table.Column(type: "numeric", nullable: true), - reward_experience = table.Column(type: "integer", nullable: true), - tips = table.Column>(type: "jsonb", nullable: false), - account_id = table.Column(type: "uuid", nullable: false), - created_at = table.Column(type: "timestamp with time zone", nullable: false), - updated_at = table.Column(type: "timestamp with time zone", nullable: false), - deleted_at = table.Column(type: "timestamp with time zone", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("pk_account_check_in_results", x => x.id); - table.ForeignKey( - name: "fk_account_check_in_results_accounts_account_id", - column: x => x.account_id, - principalTable: "accounts", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "account_contacts", - columns: table => new - { - id = table.Column(type: "uuid", nullable: false), - type = table.Column(type: "integer", nullable: false), - verified_at = table.Column(type: "timestamp with time zone", nullable: true), - content = table.Column(type: "character varying(1024)", maxLength: 1024, nullable: false), - account_id = table.Column(type: "uuid", nullable: false), - created_at = table.Column(type: "timestamp with time zone", nullable: false), - updated_at = table.Column(type: "timestamp with time zone", nullable: false), - deleted_at = table.Column(type: "timestamp with time zone", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("pk_account_contacts", x => x.id); - table.ForeignKey( - name: "fk_account_contacts_accounts_account_id", - column: x => x.account_id, - principalTable: "accounts", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "account_relationships", - columns: table => new - { - account_id = table.Column(type: "uuid", nullable: false), - related_id = table.Column(type: "uuid", nullable: false), - expired_at = table.Column(type: "timestamp with time zone", nullable: true), - status = table.Column(type: "integer", nullable: false), - created_at = table.Column(type: "timestamp with time zone", nullable: false), - updated_at = table.Column(type: "timestamp with time zone", nullable: false), - deleted_at = table.Column(type: "timestamp with time zone", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("pk_account_relationships", x => new { x.account_id, x.related_id }); - table.ForeignKey( - name: "fk_account_relationships_accounts_account_id", - column: x => x.account_id, - principalTable: "accounts", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "fk_account_relationships_accounts_related_id", - column: x => x.related_id, - principalTable: "accounts", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "account_statuses", - columns: table => new - { - id = table.Column(type: "uuid", nullable: false), - attitude = table.Column(type: "integer", nullable: false), - is_invisible = table.Column(type: "boolean", nullable: false), - is_not_disturb = table.Column(type: "boolean", nullable: false), - label = table.Column(type: "character varying(1024)", maxLength: 1024, nullable: true), - cleared_at = table.Column(type: "timestamp with time zone", nullable: true), - account_id = table.Column(type: "uuid", nullable: false), - created_at = table.Column(type: "timestamp with time zone", nullable: false), - updated_at = table.Column(type: "timestamp with time zone", nullable: false), - deleted_at = table.Column(type: "timestamp with time zone", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("pk_account_statuses", x => x.id); - table.ForeignKey( - name: "fk_account_statuses_accounts_account_id", - column: x => x.account_id, - principalTable: "accounts", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "activities", - columns: table => new - { - id = table.Column(type: "uuid", nullable: false), - type = table.Column(type: "character varying(1024)", maxLength: 1024, nullable: false), - resource_identifier = table.Column(type: "character varying(4096)", maxLength: 4096, nullable: false), - visibility = table.Column(type: "integer", nullable: false), - meta = table.Column>(type: "jsonb", nullable: false), - users_visible = table.Column>(type: "jsonb", nullable: false), - account_id = table.Column(type: "uuid", nullable: false), - created_at = table.Column(type: "timestamp with time zone", nullable: false), - updated_at = table.Column(type: "timestamp with time zone", nullable: false), - deleted_at = table.Column(type: "timestamp with time zone", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("pk_activities", x => x.id); - table.ForeignKey( - name: "fk_activities_accounts_account_id", - column: x => x.account_id, - principalTable: "accounts", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "auth_challenges", - columns: table => new - { - id = table.Column(type: "uuid", nullable: false), - expired_at = table.Column(type: "timestamp with time zone", nullable: true), - step_remain = table.Column(type: "integer", nullable: false), - step_total = table.Column(type: "integer", nullable: false), - failed_attempts = table.Column(type: "integer", nullable: false), - platform = table.Column(type: "integer", nullable: false), - type = table.Column(type: "integer", nullable: false), - blacklist_factors = table.Column>(type: "jsonb", nullable: false), - audiences = table.Column>(type: "jsonb", nullable: false), - scopes = table.Column>(type: "jsonb", nullable: false), - ip_address = table.Column(type: "character varying(128)", maxLength: 128, nullable: true), - user_agent = table.Column(type: "character varying(512)", maxLength: 512, nullable: true), - device_id = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), - nonce = table.Column(type: "character varying(1024)", maxLength: 1024, nullable: true), - location = table.Column(type: "geometry", nullable: true), - account_id = table.Column(type: "uuid", nullable: false), - created_at = table.Column(type: "timestamp with time zone", nullable: false), - updated_at = table.Column(type: "timestamp with time zone", nullable: false), - deleted_at = table.Column(type: "timestamp with time zone", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("pk_auth_challenges", x => x.id); - table.ForeignKey( - name: "fk_auth_challenges_accounts_account_id", - column: x => x.account_id, - principalTable: "accounts", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "badges", - columns: table => new - { - id = table.Column(type: "uuid", nullable: false), - type = table.Column(type: "character varying(1024)", maxLength: 1024, nullable: false), - label = table.Column(type: "character varying(1024)", maxLength: 1024, nullable: true), - caption = table.Column(type: "character varying(4096)", maxLength: 4096, nullable: true), - meta = table.Column>(type: "jsonb", nullable: false), - expired_at = table.Column(type: "timestamp with time zone", nullable: true), - account_id = table.Column(type: "uuid", nullable: false), - created_at = table.Column(type: "timestamp with time zone", nullable: false), - updated_at = table.Column(type: "timestamp with time zone", nullable: false), - deleted_at = table.Column(type: "timestamp with time zone", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("pk_badges", x => x.id); - table.ForeignKey( - name: "fk_badges_accounts_account_id", - column: x => x.account_id, - principalTable: "accounts", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "magic_spells", - columns: table => new - { - id = table.Column(type: "uuid", nullable: false), - spell = table.Column(type: "character varying(1024)", maxLength: 1024, nullable: false), - type = table.Column(type: "integer", nullable: false), - expires_at = table.Column(type: "timestamp with time zone", nullable: true), - affected_at = table.Column(type: "timestamp with time zone", nullable: true), - meta = table.Column>(type: "jsonb", nullable: false), - account_id = table.Column(type: "uuid", nullable: true), - created_at = table.Column(type: "timestamp with time zone", nullable: false), - updated_at = table.Column(type: "timestamp with time zone", nullable: false), - deleted_at = table.Column(type: "timestamp with time zone", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("pk_magic_spells", x => x.id); - table.ForeignKey( - name: "fk_magic_spells_accounts_account_id", - column: x => x.account_id, - principalTable: "accounts", - principalColumn: "id"); - }); - - migrationBuilder.CreateTable( - name: "notification_push_subscriptions", - columns: table => new - { - id = table.Column(type: "uuid", nullable: false), - device_id = table.Column(type: "character varying(4096)", maxLength: 4096, nullable: false), - device_token = table.Column(type: "character varying(4096)", maxLength: 4096, nullable: false), - provider = table.Column(type: "integer", nullable: false), - last_used_at = table.Column(type: "timestamp with time zone", nullable: true), - account_id = table.Column(type: "uuid", nullable: false), - created_at = table.Column(type: "timestamp with time zone", nullable: false), - updated_at = table.Column(type: "timestamp with time zone", nullable: false), - deleted_at = table.Column(type: "timestamp with time zone", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("pk_notification_push_subscriptions", x => x.id); - table.ForeignKey( - name: "fk_notification_push_subscriptions_accounts_account_id", - column: x => x.account_id, - principalTable: "accounts", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "notifications", - columns: table => new - { - id = table.Column(type: "uuid", nullable: false), - topic = table.Column(type: "character varying(1024)", maxLength: 1024, nullable: false), - title = table.Column(type: "character varying(1024)", maxLength: 1024, nullable: true), - subtitle = table.Column(type: "character varying(2048)", maxLength: 2048, nullable: true), - content = table.Column(type: "character varying(4096)", maxLength: 4096, nullable: true), - meta = table.Column>(type: "jsonb", nullable: true), - priority = table.Column(type: "integer", nullable: false), - viewed_at = table.Column(type: "timestamp with time zone", nullable: true), - account_id = table.Column(type: "uuid", nullable: false), - created_at = table.Column(type: "timestamp with time zone", nullable: false), - updated_at = table.Column(type: "timestamp with time zone", nullable: false), - deleted_at = table.Column(type: "timestamp with time zone", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("pk_notifications", x => x.id); - table.ForeignKey( - name: "fk_notifications_accounts_account_id", - column: x => x.account_id, - principalTable: "accounts", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "wallets", - columns: table => new - { - id = table.Column(type: "uuid", nullable: false), - account_id = table.Column(type: "uuid", nullable: false), - created_at = table.Column(type: "timestamp with time zone", nullable: false), - updated_at = table.Column(type: "timestamp with time zone", nullable: false), - deleted_at = table.Column(type: "timestamp with time zone", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("pk_wallets", x => x.id); - table.ForeignKey( - name: "fk_wallets_accounts_account_id", - column: x => x.account_id, - principalTable: "accounts", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "permission_group_members", - columns: table => new - { - group_id = table.Column(type: "uuid", nullable: false), - actor = table.Column(type: "character varying(1024)", maxLength: 1024, nullable: false), - expired_at = table.Column(type: "timestamp with time zone", nullable: true), - affected_at = table.Column(type: "timestamp with time zone", nullable: true), - created_at = table.Column(type: "timestamp with time zone", nullable: false), - updated_at = table.Column(type: "timestamp with time zone", nullable: false), - deleted_at = table.Column(type: "timestamp with time zone", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("pk_permission_group_members", x => new { x.group_id, x.actor }); - table.ForeignKey( - name: "fk_permission_group_members_permission_groups_group_id", - column: x => x.group_id, - principalTable: "permission_groups", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "permission_nodes", - columns: table => new - { - id = table.Column(type: "uuid", nullable: false), - actor = table.Column(type: "character varying(1024)", maxLength: 1024, nullable: false), - area = table.Column(type: "character varying(1024)", maxLength: 1024, nullable: false), - key = table.Column(type: "character varying(1024)", maxLength: 1024, nullable: false), - value = table.Column(type: "jsonb", nullable: false), - expired_at = table.Column(type: "timestamp with time zone", nullable: true), - affected_at = table.Column(type: "timestamp with time zone", nullable: true), - group_id = table.Column(type: "uuid", nullable: true), - created_at = table.Column(type: "timestamp with time zone", nullable: false), - updated_at = table.Column(type: "timestamp with time zone", nullable: false), - deleted_at = table.Column(type: "timestamp with time zone", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("pk_permission_nodes", x => x.id); - table.ForeignKey( - name: "fk_permission_nodes_permission_groups_group_id", - column: x => x.group_id, - principalTable: "permission_groups", - principalColumn: "id"); - }); - - migrationBuilder.CreateTable( - name: "auth_sessions", - columns: table => new - { - id = table.Column(type: "uuid", nullable: false), - label = table.Column(type: "character varying(1024)", maxLength: 1024, nullable: true), - last_granted_at = table.Column(type: "timestamp with time zone", nullable: true), - expired_at = table.Column(type: "timestamp with time zone", nullable: true), - account_id = table.Column(type: "uuid", nullable: false), - challenge_id = table.Column(type: "uuid", nullable: false), - created_at = table.Column(type: "timestamp with time zone", nullable: false), - updated_at = table.Column(type: "timestamp with time zone", nullable: false), - deleted_at = table.Column(type: "timestamp with time zone", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("pk_auth_sessions", x => x.id); - table.ForeignKey( - name: "fk_auth_sessions_accounts_account_id", - column: x => x.account_id, - principalTable: "accounts", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "fk_auth_sessions_auth_challenges_challenge_id", - column: x => x.challenge_id, - principalTable: "auth_challenges", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "payment_transactions", - columns: table => new - { - id = table.Column(type: "uuid", nullable: false), - currency = table.Column(type: "character varying(128)", maxLength: 128, nullable: false), - amount = table.Column(type: "numeric", nullable: false), - remarks = table.Column(type: "character varying(4096)", maxLength: 4096, nullable: true), - type = table.Column(type: "integer", nullable: false), - payer_wallet_id = table.Column(type: "uuid", nullable: true), - payee_wallet_id = table.Column(type: "uuid", nullable: true), - created_at = table.Column(type: "timestamp with time zone", nullable: false), - updated_at = table.Column(type: "timestamp with time zone", nullable: false), - deleted_at = table.Column(type: "timestamp with time zone", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("pk_payment_transactions", x => x.id); - table.ForeignKey( - name: "fk_payment_transactions_wallets_payee_wallet_id", - column: x => x.payee_wallet_id, - principalTable: "wallets", - principalColumn: "id"); - table.ForeignKey( - name: "fk_payment_transactions_wallets_payer_wallet_id", - column: x => x.payer_wallet_id, - principalTable: "wallets", - principalColumn: "id"); - }); - - migrationBuilder.CreateTable( - name: "wallet_pockets", - columns: table => new - { - id = table.Column(type: "uuid", nullable: false), - currency = table.Column(type: "character varying(128)", maxLength: 128, nullable: false), - amount = table.Column(type: "numeric", nullable: false), - wallet_id = table.Column(type: "uuid", nullable: false), - created_at = table.Column(type: "timestamp with time zone", nullable: false), - updated_at = table.Column(type: "timestamp with time zone", nullable: false), - deleted_at = table.Column(type: "timestamp with time zone", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("pk_wallet_pockets", x => x.id); - table.ForeignKey( - name: "fk_wallet_pockets_wallets_wallet_id", - column: x => x.wallet_id, - principalTable: "wallets", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "action_logs", - columns: table => new - { - id = table.Column(type: "uuid", nullable: false), - action = table.Column(type: "character varying(4096)", maxLength: 4096, nullable: false), - meta = table.Column>(type: "jsonb", nullable: false), - user_agent = table.Column(type: "character varying(512)", maxLength: 512, nullable: true), - ip_address = table.Column(type: "character varying(128)", maxLength: 128, nullable: true), - location = table.Column(type: "geometry", nullable: true), - account_id = table.Column(type: "uuid", nullable: false), - session_id = table.Column(type: "uuid", nullable: true), - created_at = table.Column(type: "timestamp with time zone", nullable: false), - updated_at = table.Column(type: "timestamp with time zone", nullable: false), - deleted_at = table.Column(type: "timestamp with time zone", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("pk_action_logs", x => x.id); - table.ForeignKey( - name: "fk_action_logs_accounts_account_id", - column: x => x.account_id, - principalTable: "accounts", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "fk_action_logs_auth_sessions_session_id", - column: x => x.session_id, - principalTable: "auth_sessions", - principalColumn: "id"); - }); - - migrationBuilder.CreateTable( - name: "account_profiles", - columns: table => new - { - id = table.Column(type: "uuid", nullable: false), - first_name = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), - middle_name = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), - last_name = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), - bio = table.Column(type: "character varying(4096)", maxLength: 4096, nullable: true), - experience = table.Column(type: "integer", nullable: false), - picture_id = table.Column(type: "character varying(32)", maxLength: 32, nullable: true), - background_id = table.Column(type: "character varying(32)", maxLength: 32, nullable: true), - account_id = table.Column(type: "uuid", nullable: false), - created_at = table.Column(type: "timestamp with time zone", nullable: false), - updated_at = table.Column(type: "timestamp with time zone", nullable: false), - deleted_at = table.Column(type: "timestamp with time zone", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("pk_account_profiles", x => x.id); - table.ForeignKey( - name: "fk_account_profiles_accounts_id", - column: x => x.id, - principalTable: "accounts", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "chat_members", - columns: table => new - { - id = table.Column(type: "uuid", nullable: false), - chat_room_id = table.Column(type: "uuid", nullable: false), - account_id = table.Column(type: "uuid", nullable: false), - nick = table.Column(type: "character varying(1024)", maxLength: 1024, nullable: true), - role = table.Column(type: "integer", nullable: false), - notify = table.Column(type: "integer", nullable: false), - joined_at = table.Column(type: "timestamp with time zone", nullable: true), - leave_at = table.Column(type: "timestamp with time zone", nullable: true), - is_bot = table.Column(type: "boolean", nullable: false), - created_at = table.Column(type: "timestamp with time zone", nullable: false), - updated_at = table.Column(type: "timestamp with time zone", nullable: false), - deleted_at = table.Column(type: "timestamp with time zone", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("pk_chat_members", x => x.id); - table.UniqueConstraint("ak_chat_members_chat_room_id_account_id", x => new { x.chat_room_id, x.account_id }); - table.ForeignKey( - name: "fk_chat_members_accounts_account_id", - column: x => x.account_id, - principalTable: "accounts", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "chat_messages", - columns: table => new - { - id = table.Column(type: "uuid", nullable: false), - type = table.Column(type: "text", nullable: false), - content = table.Column(type: "character varying(4096)", maxLength: 4096, nullable: true), - meta = table.Column>(type: "jsonb", nullable: true), - members_mentioned = table.Column>(type: "jsonb", nullable: true), - nonce = table.Column(type: "character varying(36)", maxLength: 36, nullable: false), - edited_at = table.Column(type: "timestamp with time zone", nullable: true), - replied_message_id = table.Column(type: "uuid", nullable: true), - forwarded_message_id = table.Column(type: "uuid", nullable: true), - sender_id = table.Column(type: "uuid", nullable: false), - chat_room_id = table.Column(type: "uuid", nullable: false), - created_at = table.Column(type: "timestamp with time zone", nullable: false), - updated_at = table.Column(type: "timestamp with time zone", nullable: false), - deleted_at = table.Column(type: "timestamp with time zone", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("pk_chat_messages", x => x.id); - table.ForeignKey( - name: "fk_chat_messages_chat_members_sender_id", - column: x => x.sender_id, - principalTable: "chat_members", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "fk_chat_messages_chat_messages_forwarded_message_id", - column: x => x.forwarded_message_id, - principalTable: "chat_messages", - principalColumn: "id", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "fk_chat_messages_chat_messages_replied_message_id", - column: x => x.replied_message_id, - principalTable: "chat_messages", - principalColumn: "id", - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.CreateTable( - name: "chat_reactions", - columns: table => new - { - id = table.Column(type: "uuid", nullable: false), - message_id = table.Column(type: "uuid", nullable: false), - sender_id = table.Column(type: "uuid", nullable: false), - symbol = table.Column(type: "character varying(256)", maxLength: 256, nullable: false), - attitude = table.Column(type: "integer", nullable: false), - created_at = table.Column(type: "timestamp with time zone", nullable: false), - updated_at = table.Column(type: "timestamp with time zone", nullable: false), - deleted_at = table.Column(type: "timestamp with time zone", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("pk_chat_reactions", x => x.id); - table.ForeignKey( - name: "fk_chat_reactions_chat_members_sender_id", - column: x => x.sender_id, - principalTable: "chat_members", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "fk_chat_reactions_chat_messages_message_id", - column: x => x.message_id, - principalTable: "chat_messages", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "chat_read_receipts", - columns: table => new - { - message_id = table.Column(type: "uuid", nullable: false), - sender_id = table.Column(type: "uuid", nullable: false), - created_at = table.Column(type: "timestamp with time zone", nullable: false), - updated_at = table.Column(type: "timestamp with time zone", nullable: false), - deleted_at = table.Column(type: "timestamp with time zone", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("pk_chat_read_receipts", x => new { x.message_id, x.sender_id }); - table.ForeignKey( - name: "fk_chat_read_receipts_chat_members_sender_id", - column: x => x.sender_id, - principalTable: "chat_members", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "fk_chat_read_receipts_chat_messages_message_id", - column: x => x.message_id, - principalTable: "chat_messages", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "chat_realtime_call", - columns: table => new - { - id = table.Column(type: "uuid", nullable: false), - title = table.Column(type: "text", nullable: true), - ended_at = table.Column(type: "timestamp with time zone", nullable: true), - sender_id = table.Column(type: "uuid", nullable: false), - room_id = table.Column(type: "uuid", nullable: false), - created_at = table.Column(type: "timestamp with time zone", nullable: false), - updated_at = table.Column(type: "timestamp with time zone", nullable: false), - deleted_at = table.Column(type: "timestamp with time zone", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("pk_chat_realtime_call", x => x.id); - table.ForeignKey( - name: "fk_chat_realtime_call_chat_members_sender_id", - column: x => x.sender_id, - principalTable: "chat_members", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "chat_rooms", - columns: table => new - { - id = table.Column(type: "uuid", nullable: false), - name = table.Column(type: "character varying(1024)", maxLength: 1024, nullable: true), - description = table.Column(type: "character varying(4096)", maxLength: 4096, nullable: true), - type = table.Column(type: "integer", nullable: false), - is_community = table.Column(type: "boolean", nullable: false), - is_public = table.Column(type: "boolean", nullable: false), - picture_id = table.Column(type: "character varying(32)", maxLength: 32, nullable: true), - background_id = table.Column(type: "character varying(32)", maxLength: 32, nullable: true), - realm_id = table.Column(type: "uuid", nullable: true), - created_at = table.Column(type: "timestamp with time zone", nullable: false), - updated_at = table.Column(type: "timestamp with time zone", nullable: false), - deleted_at = table.Column(type: "timestamp with time zone", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("pk_chat_rooms", x => x.id); - }); - - migrationBuilder.CreateTable( - name: "custom_app_secrets", - columns: table => new - { - id = table.Column(type: "uuid", nullable: false), - secret = table.Column(type: "character varying(1024)", maxLength: 1024, nullable: false), - remarks = table.Column(type: "character varying(4096)", maxLength: 4096, nullable: true), - expired_at = table.Column(type: "timestamp with time zone", nullable: true), - app_id = table.Column(type: "uuid", nullable: false), - created_at = table.Column(type: "timestamp with time zone", nullable: false), - updated_at = table.Column(type: "timestamp with time zone", nullable: false), - deleted_at = table.Column(type: "timestamp with time zone", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("pk_custom_app_secrets", x => x.id); - }); - - migrationBuilder.CreateTable( - name: "custom_apps", - columns: table => new - { - id = table.Column(type: "uuid", nullable: false), - slug = table.Column(type: "character varying(1024)", maxLength: 1024, nullable: false), - name = table.Column(type: "character varying(1024)", maxLength: 1024, nullable: false), - status = table.Column(type: "integer", nullable: false), - verified_at = table.Column(type: "timestamp with time zone", nullable: true), - verified_as = table.Column(type: "character varying(4096)", maxLength: 4096, nullable: true), - publisher_id = table.Column(type: "uuid", nullable: false), - created_at = table.Column(type: "timestamp with time zone", nullable: false), - updated_at = table.Column(type: "timestamp with time zone", nullable: false), - deleted_at = table.Column(type: "timestamp with time zone", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("pk_custom_apps", x => x.id); - }); - - migrationBuilder.CreateTable( - name: "payment_orders", - columns: table => new - { - id = table.Column(type: "uuid", nullable: false), - status = table.Column(type: "integer", nullable: false), - currency = table.Column(type: "character varying(128)", maxLength: 128, nullable: false), - remarks = table.Column(type: "character varying(4096)", maxLength: 4096, nullable: true), - amount = table.Column(type: "numeric", nullable: false), - expired_at = table.Column(type: "timestamp with time zone", nullable: false), - payee_wallet_id = table.Column(type: "uuid", nullable: false), - transaction_id = table.Column(type: "uuid", nullable: true), - issuer_app_id = table.Column(type: "uuid", nullable: true), - created_at = table.Column(type: "timestamp with time zone", nullable: false), - updated_at = table.Column(type: "timestamp with time zone", nullable: false), - deleted_at = table.Column(type: "timestamp with time zone", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("pk_payment_orders", x => x.id); - table.ForeignKey( - name: "fk_payment_orders_custom_apps_issuer_app_id", - column: x => x.issuer_app_id, - principalTable: "custom_apps", - principalColumn: "id"); - table.ForeignKey( - name: "fk_payment_orders_payment_transactions_transaction_id", - column: x => x.transaction_id, - principalTable: "payment_transactions", - principalColumn: "id"); - table.ForeignKey( - name: "fk_payment_orders_wallets_payee_wallet_id", - column: x => x.payee_wallet_id, - principalTable: "wallets", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "files", - columns: table => new - { - id = table.Column(type: "character varying(32)", maxLength: 32, nullable: false), - name = table.Column(type: "character varying(1024)", maxLength: 1024, nullable: false), - description = table.Column(type: "character varying(4096)", maxLength: 4096, nullable: true), - file_meta = table.Column>(type: "jsonb", nullable: true), - user_meta = table.Column>(type: "jsonb", nullable: true), - sensitive_marks = table.Column>(type: "jsonb", nullable: true), - mime_type = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), - hash = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), - size = table.Column(type: "bigint", nullable: false), - uploaded_at = table.Column(type: "timestamp with time zone", nullable: true), - expired_at = table.Column(type: "timestamp with time zone", nullable: true), - uploaded_to = table.Column(type: "character varying(128)", maxLength: 128, nullable: true), - has_compression = table.Column(type: "boolean", nullable: false), - storage_id = table.Column(type: "character varying(32)", maxLength: 32, nullable: true), - storage_url = table.Column(type: "character varying(4096)", maxLength: 4096, nullable: true), - used_count = table.Column(type: "integer", nullable: false), - account_id = table.Column(type: "uuid", nullable: false), - message_id = table.Column(type: "uuid", nullable: true), - post_id = table.Column(type: "uuid", nullable: true), - created_at = table.Column(type: "timestamp with time zone", nullable: false), - updated_at = table.Column(type: "timestamp with time zone", nullable: false), - deleted_at = table.Column(type: "timestamp with time zone", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("pk_files", x => x.id); - table.ForeignKey( - name: "fk_files_accounts_account_id", - column: x => x.account_id, - principalTable: "accounts", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "fk_files_chat_messages_message_id", - column: x => x.message_id, - principalTable: "chat_messages", - principalColumn: "id"); - }); - - migrationBuilder.CreateTable( - name: "realms", - columns: table => new - { - id = table.Column(type: "uuid", nullable: false), - slug = table.Column(type: "character varying(1024)", maxLength: 1024, nullable: false), - name = table.Column(type: "character varying(1024)", maxLength: 1024, nullable: false), - description = table.Column(type: "character varying(4096)", maxLength: 4096, nullable: false), - verified_as = table.Column(type: "character varying(4096)", maxLength: 4096, nullable: true), - verified_at = table.Column(type: "timestamp with time zone", nullable: true), - is_community = table.Column(type: "boolean", nullable: false), - is_public = table.Column(type: "boolean", nullable: false), - picture_id = table.Column(type: "character varying(32)", maxLength: 32, nullable: true), - background_id = table.Column(type: "character varying(32)", maxLength: 32, nullable: true), - account_id = table.Column(type: "uuid", nullable: false), - created_at = table.Column(type: "timestamp with time zone", nullable: false), - updated_at = table.Column(type: "timestamp with time zone", nullable: false), - deleted_at = table.Column(type: "timestamp with time zone", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("pk_realms", x => x.id); - table.ForeignKey( - name: "fk_realms_accounts_account_id", - column: x => x.account_id, - principalTable: "accounts", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "fk_realms_files_background_id", - column: x => x.background_id, - principalTable: "files", - principalColumn: "id"); - table.ForeignKey( - name: "fk_realms_files_picture_id", - column: x => x.picture_id, - principalTable: "files", - principalColumn: "id"); - }); - - migrationBuilder.CreateTable( - name: "publishers", - columns: table => new - { - id = table.Column(type: "uuid", nullable: false), - type = table.Column(type: "integer", nullable: false), - name = table.Column(type: "character varying(256)", maxLength: 256, nullable: false), - nick = table.Column(type: "character varying(256)", maxLength: 256, nullable: false), - bio = table.Column(type: "character varying(4096)", maxLength: 4096, nullable: true), - picture_id = table.Column(type: "character varying(32)", nullable: true), - background_id = table.Column(type: "character varying(32)", nullable: true), - account_id = table.Column(type: "uuid", nullable: true), - realm_id = table.Column(type: "uuid", nullable: true), - created_at = table.Column(type: "timestamp with time zone", nullable: false), - updated_at = table.Column(type: "timestamp with time zone", nullable: false), - deleted_at = table.Column(type: "timestamp with time zone", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("pk_publishers", x => x.id); - table.ForeignKey( - name: "fk_publishers_accounts_account_id", - column: x => x.account_id, - principalTable: "accounts", - principalColumn: "id"); - table.ForeignKey( - name: "fk_publishers_files_background_id", - column: x => x.background_id, - principalTable: "files", - principalColumn: "id"); - table.ForeignKey( - name: "fk_publishers_files_picture_id", - column: x => x.picture_id, - principalTable: "files", - principalColumn: "id"); - table.ForeignKey( - name: "fk_publishers_realms_realm_id", - column: x => x.realm_id, - principalTable: "realms", - principalColumn: "id"); - }); - - migrationBuilder.CreateTable( - name: "realm_members", - columns: table => new - { - realm_id = table.Column(type: "uuid", nullable: false), - account_id = table.Column(type: "uuid", nullable: false), - role = table.Column(type: "integer", nullable: false), - joined_at = table.Column(type: "timestamp with time zone", nullable: true), - leave_at = table.Column(type: "timestamp with time zone", nullable: true), - created_at = table.Column(type: "timestamp with time zone", nullable: false), - updated_at = table.Column(type: "timestamp with time zone", nullable: false), - deleted_at = table.Column(type: "timestamp with time zone", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("pk_realm_members", x => new { x.realm_id, x.account_id }); - table.ForeignKey( - name: "fk_realm_members_accounts_account_id", - column: x => x.account_id, - principalTable: "accounts", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "fk_realm_members_realms_realm_id", - column: x => x.realm_id, - principalTable: "realms", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "post_collections", - columns: table => new - { - id = table.Column(type: "uuid", nullable: false), - slug = table.Column(type: "character varying(128)", maxLength: 128, nullable: false), - name = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), - description = table.Column(type: "character varying(4096)", maxLength: 4096, nullable: true), - publisher_id = table.Column(type: "uuid", nullable: false), - created_at = table.Column(type: "timestamp with time zone", nullable: false), - updated_at = table.Column(type: "timestamp with time zone", nullable: false), - deleted_at = table.Column(type: "timestamp with time zone", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("pk_post_collections", x => x.id); - table.ForeignKey( - name: "fk_post_collections_publishers_publisher_id", - column: x => x.publisher_id, - principalTable: "publishers", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "posts", - columns: table => new - { - id = table.Column(type: "uuid", nullable: false), - title = table.Column(type: "character varying(1024)", maxLength: 1024, nullable: true), - description = table.Column(type: "character varying(4096)", maxLength: 4096, nullable: true), - language = table.Column(type: "character varying(128)", maxLength: 128, nullable: true), - edited_at = table.Column(type: "timestamp with time zone", nullable: true), - published_at = table.Column(type: "timestamp with time zone", nullable: true), - visibility = table.Column(type: "integer", nullable: false), - content = table.Column(type: "text", nullable: true), - type = table.Column(type: "integer", nullable: false), - meta = table.Column>(type: "jsonb", nullable: true), - views_unique = table.Column(type: "integer", nullable: false), - views_total = table.Column(type: "integer", nullable: false), - upvotes = table.Column(type: "integer", nullable: false), - downvotes = table.Column(type: "integer", nullable: false), - threaded_post_id = table.Column(type: "uuid", nullable: true), - replied_post_id = table.Column(type: "uuid", nullable: true), - forwarded_post_id = table.Column(type: "uuid", nullable: true), - search_vector = table.Column(type: "tsvector", nullable: false) - .Annotation("Npgsql:TsVectorConfig", "simple") - .Annotation("Npgsql:TsVectorProperties", new[] { "title", "description", "content" }), - publisher_id = table.Column(type: "uuid", nullable: false), - created_at = table.Column(type: "timestamp with time zone", nullable: false), - updated_at = table.Column(type: "timestamp with time zone", nullable: false), - deleted_at = table.Column(type: "timestamp with time zone", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("pk_posts", x => x.id); - table.ForeignKey( - name: "fk_posts_posts_forwarded_post_id", - column: x => x.forwarded_post_id, - principalTable: "posts", - principalColumn: "id", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "fk_posts_posts_replied_post_id", - column: x => x.replied_post_id, - principalTable: "posts", - principalColumn: "id", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "fk_posts_posts_threaded_post_id", - column: x => x.threaded_post_id, - principalTable: "posts", - principalColumn: "id"); - table.ForeignKey( - name: "fk_posts_publishers_publisher_id", - column: x => x.publisher_id, - principalTable: "publishers", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "publisher_features", - columns: table => new - { - id = table.Column(type: "uuid", nullable: false), - flag = table.Column(type: "character varying(1024)", maxLength: 1024, nullable: false), - expired_at = table.Column(type: "timestamp with time zone", nullable: true), - publisher_id = table.Column(type: "uuid", nullable: false), - created_at = table.Column(type: "timestamp with time zone", nullable: false), - updated_at = table.Column(type: "timestamp with time zone", nullable: false), - deleted_at = table.Column(type: "timestamp with time zone", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("pk_publisher_features", x => x.id); - table.ForeignKey( - name: "fk_publisher_features_publishers_publisher_id", - column: x => x.publisher_id, - principalTable: "publishers", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "publisher_members", - columns: table => new - { - publisher_id = table.Column(type: "uuid", nullable: false), - account_id = table.Column(type: "uuid", nullable: false), - role = table.Column(type: "integer", nullable: false), - joined_at = table.Column(type: "timestamp with time zone", nullable: true), - created_at = table.Column(type: "timestamp with time zone", nullable: false), - updated_at = table.Column(type: "timestamp with time zone", nullable: false), - deleted_at = table.Column(type: "timestamp with time zone", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("pk_publisher_members", x => new { x.publisher_id, x.account_id }); - table.ForeignKey( - name: "fk_publisher_members_accounts_account_id", - column: x => x.account_id, - principalTable: "accounts", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "fk_publisher_members_publishers_publisher_id", - column: x => x.publisher_id, - principalTable: "publishers", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "publisher_subscriptions", - columns: table => new - { - id = table.Column(type: "uuid", nullable: false), - publisher_id = table.Column(type: "uuid", nullable: false), - account_id = table.Column(type: "uuid", nullable: false), - status = table.Column(type: "integer", nullable: false), - tier = table.Column(type: "integer", nullable: false), - created_at = table.Column(type: "timestamp with time zone", nullable: false), - updated_at = table.Column(type: "timestamp with time zone", nullable: false), - deleted_at = table.Column(type: "timestamp with time zone", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("pk_publisher_subscriptions", x => x.id); - table.ForeignKey( - name: "fk_publisher_subscriptions_accounts_account_id", - column: x => x.account_id, - principalTable: "accounts", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "fk_publisher_subscriptions_publishers_publisher_id", - column: x => x.publisher_id, - principalTable: "publishers", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "sticker_packs", - columns: table => new - { - id = table.Column(type: "uuid", nullable: false), - name = table.Column(type: "character varying(1024)", maxLength: 1024, nullable: false), - description = table.Column(type: "character varying(4096)", maxLength: 4096, nullable: false), - prefix = table.Column(type: "character varying(128)", maxLength: 128, nullable: false), - publisher_id = table.Column(type: "uuid", nullable: false), - created_at = table.Column(type: "timestamp with time zone", nullable: false), - updated_at = table.Column(type: "timestamp with time zone", nullable: false), - deleted_at = table.Column(type: "timestamp with time zone", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("pk_sticker_packs", x => x.id); - table.ForeignKey( - name: "fk_sticker_packs_publishers_publisher_id", - column: x => x.publisher_id, - principalTable: "publishers", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "post_category_links", - columns: table => new - { - categories_id = table.Column(type: "uuid", nullable: false), - posts_id = table.Column(type: "uuid", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("pk_post_category_links", x => new { x.categories_id, x.posts_id }); - table.ForeignKey( - name: "fk_post_category_links_post_categories_categories_id", - column: x => x.categories_id, - principalTable: "post_categories", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "fk_post_category_links_posts_posts_id", - column: x => x.posts_id, - principalTable: "posts", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "post_collection_links", - columns: table => new - { - collections_id = table.Column(type: "uuid", nullable: false), - posts_id = table.Column(type: "uuid", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("pk_post_collection_links", x => new { x.collections_id, x.posts_id }); - table.ForeignKey( - name: "fk_post_collection_links_post_collections_collections_id", - column: x => x.collections_id, - principalTable: "post_collections", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "fk_post_collection_links_posts_posts_id", - column: x => x.posts_id, - principalTable: "posts", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "post_reactions", - columns: table => new - { - id = table.Column(type: "uuid", nullable: false), - symbol = table.Column(type: "character varying(256)", maxLength: 256, nullable: false), - attitude = table.Column(type: "integer", nullable: false), - post_id = table.Column(type: "uuid", nullable: false), - account_id = table.Column(type: "uuid", nullable: false), - created_at = table.Column(type: "timestamp with time zone", nullable: false), - updated_at = table.Column(type: "timestamp with time zone", nullable: false), - deleted_at = table.Column(type: "timestamp with time zone", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("pk_post_reactions", x => x.id); - table.ForeignKey( - name: "fk_post_reactions_accounts_account_id", - column: x => x.account_id, - principalTable: "accounts", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "fk_post_reactions_posts_post_id", - column: x => x.post_id, - principalTable: "posts", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "post_tag_links", - columns: table => new - { - posts_id = table.Column(type: "uuid", nullable: false), - tags_id = table.Column(type: "uuid", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("pk_post_tag_links", x => new { x.posts_id, x.tags_id }); - table.ForeignKey( - name: "fk_post_tag_links_post_tags_tags_id", - column: x => x.tags_id, - principalTable: "post_tags", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "fk_post_tag_links_posts_posts_id", - column: x => x.posts_id, - principalTable: "posts", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "stickers", - columns: table => new - { - id = table.Column(type: "uuid", nullable: false), - slug = table.Column(type: "character varying(128)", maxLength: 128, nullable: false), - image_id = table.Column(type: "character varying(32)", maxLength: 32, nullable: false), - pack_id = table.Column(type: "uuid", nullable: false), - created_at = table.Column(type: "timestamp with time zone", nullable: false), - updated_at = table.Column(type: "timestamp with time zone", nullable: false), - deleted_at = table.Column(type: "timestamp with time zone", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("pk_stickers", x => x.id); - table.ForeignKey( - name: "fk_stickers_files_image_id", - column: x => x.image_id, - principalTable: "files", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "fk_stickers_sticker_packs_pack_id", - column: x => x.pack_id, - principalTable: "sticker_packs", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateIndex( - name: "ix_account_auth_factors_account_id", - table: "account_auth_factors", - column: "account_id"); - - migrationBuilder.CreateIndex( - name: "ix_account_check_in_results_account_id", - table: "account_check_in_results", - column: "account_id"); - - migrationBuilder.CreateIndex( - name: "ix_account_contacts_account_id", - table: "account_contacts", - column: "account_id"); - - migrationBuilder.CreateIndex( - name: "ix_account_profiles_background_id", - table: "account_profiles", - column: "background_id"); - - migrationBuilder.CreateIndex( - name: "ix_account_profiles_picture_id", - table: "account_profiles", - column: "picture_id"); - - migrationBuilder.CreateIndex( - name: "ix_account_relationships_related_id", - table: "account_relationships", - column: "related_id"); - - migrationBuilder.CreateIndex( - name: "ix_account_statuses_account_id", - table: "account_statuses", - column: "account_id"); - - migrationBuilder.CreateIndex( - name: "ix_accounts_name", - table: "accounts", - column: "name", - unique: true); - - migrationBuilder.CreateIndex( - name: "ix_action_logs_account_id", - table: "action_logs", - column: "account_id"); - - migrationBuilder.CreateIndex( - name: "ix_action_logs_session_id", - table: "action_logs", - column: "session_id"); - - migrationBuilder.CreateIndex( - name: "ix_activities_account_id", - table: "activities", - column: "account_id"); - - migrationBuilder.CreateIndex( - name: "ix_auth_challenges_account_id", - table: "auth_challenges", - column: "account_id"); - - migrationBuilder.CreateIndex( - name: "ix_auth_sessions_account_id", - table: "auth_sessions", - column: "account_id"); - - migrationBuilder.CreateIndex( - name: "ix_auth_sessions_challenge_id", - table: "auth_sessions", - column: "challenge_id"); - - migrationBuilder.CreateIndex( - name: "ix_badges_account_id", - table: "badges", - column: "account_id"); - - migrationBuilder.CreateIndex( - name: "ix_chat_members_account_id", - table: "chat_members", - column: "account_id"); - - migrationBuilder.CreateIndex( - name: "ix_chat_messages_chat_room_id", - table: "chat_messages", - column: "chat_room_id"); - - migrationBuilder.CreateIndex( - name: "ix_chat_messages_forwarded_message_id", - table: "chat_messages", - column: "forwarded_message_id"); - - migrationBuilder.CreateIndex( - name: "ix_chat_messages_replied_message_id", - table: "chat_messages", - column: "replied_message_id"); - - migrationBuilder.CreateIndex( - name: "ix_chat_messages_sender_id", - table: "chat_messages", - column: "sender_id"); - - migrationBuilder.CreateIndex( - name: "ix_chat_reactions_message_id", - table: "chat_reactions", - column: "message_id"); - - migrationBuilder.CreateIndex( - name: "ix_chat_reactions_sender_id", - table: "chat_reactions", - column: "sender_id"); - - migrationBuilder.CreateIndex( - name: "ix_chat_read_receipts_message_id_sender_id", - table: "chat_read_receipts", - columns: new[] { "message_id", "sender_id" }, - unique: true); - - migrationBuilder.CreateIndex( - name: "ix_chat_read_receipts_sender_id", - table: "chat_read_receipts", - column: "sender_id"); - - migrationBuilder.CreateIndex( - name: "ix_chat_realtime_call_room_id", - table: "chat_realtime_call", - column: "room_id"); - - migrationBuilder.CreateIndex( - name: "ix_chat_realtime_call_sender_id", - table: "chat_realtime_call", - column: "sender_id"); - - migrationBuilder.CreateIndex( - name: "ix_chat_rooms_background_id", - table: "chat_rooms", - column: "background_id"); - - migrationBuilder.CreateIndex( - name: "ix_chat_rooms_picture_id", - table: "chat_rooms", - column: "picture_id"); - - migrationBuilder.CreateIndex( - name: "ix_chat_rooms_realm_id", - table: "chat_rooms", - column: "realm_id"); - - migrationBuilder.CreateIndex( - name: "ix_custom_app_secrets_app_id", - table: "custom_app_secrets", - column: "app_id"); - - migrationBuilder.CreateIndex( - name: "ix_custom_apps_publisher_id", - table: "custom_apps", - column: "publisher_id"); - - migrationBuilder.CreateIndex( - name: "ix_files_account_id", - table: "files", - column: "account_id"); - - migrationBuilder.CreateIndex( - name: "ix_files_message_id", - table: "files", - column: "message_id"); - - migrationBuilder.CreateIndex( - name: "ix_files_post_id", - table: "files", - column: "post_id"); - - migrationBuilder.CreateIndex( - name: "ix_magic_spells_account_id", - table: "magic_spells", - column: "account_id"); - - migrationBuilder.CreateIndex( - name: "ix_magic_spells_spell", - table: "magic_spells", - column: "spell", - unique: true); - - migrationBuilder.CreateIndex( - name: "ix_notification_push_subscriptions_account_id", - table: "notification_push_subscriptions", - column: "account_id"); - - migrationBuilder.CreateIndex( - name: "ix_notification_push_subscriptions_device_id", - table: "notification_push_subscriptions", - column: "device_id", - unique: true); - - migrationBuilder.CreateIndex( - name: "ix_notification_push_subscriptions_device_token", - table: "notification_push_subscriptions", - column: "device_token", - unique: true); - - migrationBuilder.CreateIndex( - name: "ix_notifications_account_id", - table: "notifications", - column: "account_id"); - - migrationBuilder.CreateIndex( - name: "ix_payment_orders_issuer_app_id", - table: "payment_orders", - column: "issuer_app_id"); - - migrationBuilder.CreateIndex( - name: "ix_payment_orders_payee_wallet_id", - table: "payment_orders", - column: "payee_wallet_id"); - - migrationBuilder.CreateIndex( - name: "ix_payment_orders_transaction_id", - table: "payment_orders", - column: "transaction_id"); - - migrationBuilder.CreateIndex( - name: "ix_payment_transactions_payee_wallet_id", - table: "payment_transactions", - column: "payee_wallet_id"); - - migrationBuilder.CreateIndex( - name: "ix_payment_transactions_payer_wallet_id", - table: "payment_transactions", - column: "payer_wallet_id"); - - migrationBuilder.CreateIndex( - name: "ix_permission_nodes_group_id", - table: "permission_nodes", - column: "group_id"); - - migrationBuilder.CreateIndex( - name: "ix_permission_nodes_key_area_actor", - table: "permission_nodes", - columns: new[] { "key", "area", "actor" }); - - migrationBuilder.CreateIndex( - name: "ix_post_category_links_posts_id", - table: "post_category_links", - column: "posts_id"); - - migrationBuilder.CreateIndex( - name: "ix_post_collection_links_posts_id", - table: "post_collection_links", - column: "posts_id"); - - migrationBuilder.CreateIndex( - name: "ix_post_collections_publisher_id", - table: "post_collections", - column: "publisher_id"); - - migrationBuilder.CreateIndex( - name: "ix_post_reactions_account_id", - table: "post_reactions", - column: "account_id"); - - migrationBuilder.CreateIndex( - name: "ix_post_reactions_post_id", - table: "post_reactions", - column: "post_id"); - - migrationBuilder.CreateIndex( - name: "ix_post_tag_links_tags_id", - table: "post_tag_links", - column: "tags_id"); - - migrationBuilder.CreateIndex( - name: "ix_posts_forwarded_post_id", - table: "posts", - column: "forwarded_post_id"); - - migrationBuilder.CreateIndex( - name: "ix_posts_publisher_id", - table: "posts", - column: "publisher_id"); - - migrationBuilder.CreateIndex( - name: "ix_posts_replied_post_id", - table: "posts", - column: "replied_post_id"); - - migrationBuilder.CreateIndex( - name: "ix_posts_search_vector", - table: "posts", - column: "search_vector") - .Annotation("Npgsql:IndexMethod", "GIN"); - - migrationBuilder.CreateIndex( - name: "ix_posts_threaded_post_id", - table: "posts", - column: "threaded_post_id", - unique: true); - - migrationBuilder.CreateIndex( - name: "ix_publisher_features_publisher_id", - table: "publisher_features", - column: "publisher_id"); - - migrationBuilder.CreateIndex( - name: "ix_publisher_members_account_id", - table: "publisher_members", - column: "account_id"); - - migrationBuilder.CreateIndex( - name: "ix_publisher_subscriptions_account_id", - table: "publisher_subscriptions", - column: "account_id"); - - migrationBuilder.CreateIndex( - name: "ix_publisher_subscriptions_publisher_id", - table: "publisher_subscriptions", - column: "publisher_id"); - - migrationBuilder.CreateIndex( - name: "ix_publishers_account_id", - table: "publishers", - column: "account_id"); - - migrationBuilder.CreateIndex( - name: "ix_publishers_background_id", - table: "publishers", - column: "background_id"); - - migrationBuilder.CreateIndex( - name: "ix_publishers_name", - table: "publishers", - column: "name", - unique: true); - - migrationBuilder.CreateIndex( - name: "ix_publishers_picture_id", - table: "publishers", - column: "picture_id"); - - migrationBuilder.CreateIndex( - name: "ix_publishers_realm_id", - table: "publishers", - column: "realm_id"); - - migrationBuilder.CreateIndex( - name: "ix_realm_members_account_id", - table: "realm_members", - column: "account_id"); - - migrationBuilder.CreateIndex( - name: "ix_realms_account_id", - table: "realms", - column: "account_id"); - - migrationBuilder.CreateIndex( - name: "ix_realms_background_id", - table: "realms", - column: "background_id"); - - migrationBuilder.CreateIndex( - name: "ix_realms_picture_id", - table: "realms", - column: "picture_id"); - - migrationBuilder.CreateIndex( - name: "ix_realms_slug", - table: "realms", - column: "slug", - unique: true); - - migrationBuilder.CreateIndex( - name: "ix_sticker_packs_prefix", - table: "sticker_packs", - column: "prefix", - unique: true); - - migrationBuilder.CreateIndex( - name: "ix_sticker_packs_publisher_id", - table: "sticker_packs", - column: "publisher_id"); - - migrationBuilder.CreateIndex( - name: "ix_stickers_image_id", - table: "stickers", - column: "image_id"); - - migrationBuilder.CreateIndex( - name: "ix_stickers_pack_id", - table: "stickers", - column: "pack_id"); - - migrationBuilder.CreateIndex( - name: "ix_stickers_slug", - table: "stickers", - column: "slug"); - - migrationBuilder.CreateIndex( - name: "ix_wallet_pockets_wallet_id", - table: "wallet_pockets", - column: "wallet_id"); - - migrationBuilder.CreateIndex( - name: "ix_wallets_account_id", - table: "wallets", - column: "account_id"); - - migrationBuilder.AddForeignKey( - name: "fk_account_profiles_files_background_id", - table: "account_profiles", - column: "background_id", - principalTable: "files", - principalColumn: "id"); - - migrationBuilder.AddForeignKey( - name: "fk_account_profiles_files_picture_id", - table: "account_profiles", - column: "picture_id", - principalTable: "files", - principalColumn: "id"); - - migrationBuilder.AddForeignKey( - name: "fk_chat_members_chat_rooms_chat_room_id", - table: "chat_members", - column: "chat_room_id", - principalTable: "chat_rooms", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - - migrationBuilder.AddForeignKey( - name: "fk_chat_messages_chat_rooms_chat_room_id", - table: "chat_messages", - column: "chat_room_id", - principalTable: "chat_rooms", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - - migrationBuilder.AddForeignKey( - name: "fk_chat_realtime_call_chat_rooms_room_id", - table: "chat_realtime_call", - column: "room_id", - principalTable: "chat_rooms", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - - migrationBuilder.AddForeignKey( - name: "fk_chat_rooms_files_background_id", - table: "chat_rooms", - column: "background_id", - principalTable: "files", - principalColumn: "id"); - - migrationBuilder.AddForeignKey( - name: "fk_chat_rooms_files_picture_id", - table: "chat_rooms", - column: "picture_id", - principalTable: "files", - principalColumn: "id"); - - migrationBuilder.AddForeignKey( - name: "fk_chat_rooms_realms_realm_id", - table: "chat_rooms", - column: "realm_id", - principalTable: "realms", - principalColumn: "id"); - - migrationBuilder.AddForeignKey( - name: "fk_custom_app_secrets_custom_apps_app_id", - table: "custom_app_secrets", - column: "app_id", - principalTable: "custom_apps", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - - migrationBuilder.AddForeignKey( - name: "fk_custom_apps_publishers_publisher_id", - table: "custom_apps", - column: "publisher_id", - principalTable: "publishers", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - - migrationBuilder.AddForeignKey( - name: "fk_files_posts_post_id", - table: "files", - column: "post_id", - principalTable: "posts", - principalColumn: "id"); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropForeignKey( - name: "fk_chat_members_accounts_account_id", - table: "chat_members"); - - migrationBuilder.DropForeignKey( - name: "fk_files_accounts_account_id", - table: "files"); - - migrationBuilder.DropForeignKey( - name: "fk_publishers_accounts_account_id", - table: "publishers"); - - migrationBuilder.DropForeignKey( - name: "fk_realms_accounts_account_id", - table: "realms"); - - migrationBuilder.DropForeignKey( - name: "fk_chat_rooms_files_background_id", - table: "chat_rooms"); - - migrationBuilder.DropForeignKey( - name: "fk_chat_rooms_files_picture_id", - table: "chat_rooms"); - - migrationBuilder.DropForeignKey( - name: "fk_publishers_files_background_id", - table: "publishers"); - - migrationBuilder.DropForeignKey( - name: "fk_publishers_files_picture_id", - table: "publishers"); - - migrationBuilder.DropForeignKey( - name: "fk_realms_files_background_id", - table: "realms"); - - migrationBuilder.DropForeignKey( - name: "fk_realms_files_picture_id", - table: "realms"); - - migrationBuilder.DropTable( - name: "account_auth_factors"); - - migrationBuilder.DropTable( - name: "account_check_in_results"); - - migrationBuilder.DropTable( - name: "account_contacts"); - - migrationBuilder.DropTable( - name: "account_profiles"); - - migrationBuilder.DropTable( - name: "account_relationships"); - - migrationBuilder.DropTable( - name: "account_statuses"); - - migrationBuilder.DropTable( - name: "action_logs"); - - migrationBuilder.DropTable( - name: "activities"); - - migrationBuilder.DropTable( - name: "badges"); - - migrationBuilder.DropTable( - name: "chat_reactions"); - - migrationBuilder.DropTable( - name: "chat_read_receipts"); - - migrationBuilder.DropTable( - name: "chat_realtime_call"); - - migrationBuilder.DropTable( - name: "custom_app_secrets"); - - migrationBuilder.DropTable( - name: "magic_spells"); - - migrationBuilder.DropTable( - name: "notification_push_subscriptions"); - - migrationBuilder.DropTable( - name: "notifications"); - - migrationBuilder.DropTable( - name: "payment_orders"); - - migrationBuilder.DropTable( - name: "permission_group_members"); - - migrationBuilder.DropTable( - name: "permission_nodes"); - - migrationBuilder.DropTable( - name: "post_category_links"); - - migrationBuilder.DropTable( - name: "post_collection_links"); - - migrationBuilder.DropTable( - name: "post_reactions"); - - migrationBuilder.DropTable( - name: "post_tag_links"); - - migrationBuilder.DropTable( - name: "publisher_features"); - - migrationBuilder.DropTable( - name: "publisher_members"); - - migrationBuilder.DropTable( - name: "publisher_subscriptions"); - - migrationBuilder.DropTable( - name: "realm_members"); - - migrationBuilder.DropTable( - name: "stickers"); - - migrationBuilder.DropTable( - name: "wallet_pockets"); - - migrationBuilder.DropTable( - name: "auth_sessions"); - - migrationBuilder.DropTable( - name: "custom_apps"); - - migrationBuilder.DropTable( - name: "payment_transactions"); - - migrationBuilder.DropTable( - name: "permission_groups"); - - migrationBuilder.DropTable( - name: "post_categories"); - - migrationBuilder.DropTable( - name: "post_collections"); - - migrationBuilder.DropTable( - name: "post_tags"); - - migrationBuilder.DropTable( - name: "sticker_packs"); - - migrationBuilder.DropTable( - name: "auth_challenges"); - - migrationBuilder.DropTable( - name: "wallets"); - - migrationBuilder.DropTable( - name: "accounts"); - - migrationBuilder.DropTable( - name: "files"); - - migrationBuilder.DropTable( - name: "chat_messages"); - - migrationBuilder.DropTable( - name: "posts"); - - migrationBuilder.DropTable( - name: "chat_members"); - - migrationBuilder.DropTable( - name: "publishers"); - - migrationBuilder.DropTable( - name: "chat_rooms"); - - migrationBuilder.DropTable( - name: "realms"); - } - } -} diff --git a/DysonNetwork.Sphere/Migrations/20250521142845_EnrichAccountProfile.Designer.cs b/DysonNetwork.Sphere/Migrations/20250521142845_EnrichAccountProfile.Designer.cs deleted file mode 100644 index 19f36e5..0000000 --- a/DysonNetwork.Sphere/Migrations/20250521142845_EnrichAccountProfile.Designer.cs +++ /dev/null @@ -1,3444 +0,0 @@ -// -using System; -using System.Collections.Generic; -using System.Text.Json; -using DysonNetwork.Sphere; -using DysonNetwork.Sphere.Account; -using DysonNetwork.Sphere.Storage; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using NetTopologySuite.Geometries; -using NodaTime; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; -using NpgsqlTypes; - -#nullable disable - -namespace DysonNetwork.Sphere.Migrations -{ - [DbContext(typeof(AppDatabase))] - [Migration("20250521142845_EnrichAccountProfile")] - partial class EnrichAccountProfile - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "9.0.3") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "postgis"); - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Account", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("ActivatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("activated_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("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("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.Sphere.Account.AccountAuthFactor", 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("Secret") - .HasMaxLength(8196) - .HasColumnType("character varying(8196)") - .HasColumnName("secret"); - - 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.Sphere.Account.AccountContact", 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("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.Sphere.Account.ActionLog", 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("geometry") - .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.HasIndex("SessionId") - .HasDatabaseName("ix_action_logs_session_id"); - - b.ToTable("action_logs", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Badge", 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("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.Sphere.Account.CheckInResult", 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("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.Sphere.Account.MagicSpell", 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.Sphere.Account.Notification", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("Content") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .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>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("Priority") - .HasColumnType("integer") - .HasColumnName("priority"); - - b.Property("Subtitle") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("subtitle"); - - b.Property("Title") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("title"); - - b.Property("Topic") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("topic"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("ViewedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("viewed_at"); - - b.HasKey("Id") - .HasName("pk_notifications"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_notifications_account_id"); - - b.ToTable("notifications", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.NotificationPushSubscription", 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(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("device_id"); - - b.Property("DeviceToken") - .IsRequired() - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("device_token"); - - b.Property("LastUsedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_used_at"); - - b.Property("Provider") - .HasColumnType("integer") - .HasColumnName("provider"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_notification_push_subscriptions"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_notification_push_subscriptions_account_id"); - - b.HasIndex("DeviceId") - .IsUnique() - .HasDatabaseName("ix_notification_push_subscriptions_device_id"); - - b.HasIndex("DeviceToken") - .IsUnique() - .HasDatabaseName("ix_notification_push_subscriptions_device_token"); - - b.ToTable("notification_push_subscriptions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Profile", b => - { - b.Property("Id") - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_id"); - - 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("MiddleName") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("middle_name"); - - b.Property("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - b.Property("Pronouns") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("pronouns"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_account_profiles"); - - b.HasIndex("BackgroundId") - .HasDatabaseName("ix_account_profiles_background_id"); - - b.HasIndex("PictureId") - .HasDatabaseName("ix_account_profiles_picture_id"); - - b.ToTable("account_profiles", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Relationship", 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("integer") - .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.Sphere.Account.Status", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - 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("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("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.Sphere.Activity.Activity", 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>("Meta") - .IsRequired() - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("ResourceIdentifier") - .IsRequired() - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("resource_identifier"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property>("UsersVisible") - .IsRequired() - .HasColumnType("jsonb") - .HasColumnName("users_visible"); - - b.Property("Visibility") - .HasColumnType("integer") - .HasColumnName("visibility"); - - b.HasKey("Id") - .HasName("pk_activities"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_activities_account_id"); - - b.ToTable("activities", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Challenge", 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") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("device_id"); - - 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("geometry") - .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("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_challenges"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_auth_challenges_account_id"); - - b.ToTable("auth_challenges", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Session", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("ChallengeId") - .HasColumnType("uuid") - .HasColumnName("challenge_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("Label") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("label"); - - b.Property("LastGrantedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_granted_at"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - 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.ToTable("auth_sessions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatMember", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("ChatRoomId") - .HasColumnType("uuid") - .HasColumnName("chat_room_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("IsBot") - .HasColumnType("boolean") - .HasColumnName("is_bot"); - - b.Property("JoinedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("joined_at"); - - b.Property("LeaveAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("leave_at"); - - b.Property("Nick") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("nick"); - - b.Property("Notify") - .HasColumnType("integer") - .HasColumnName("notify"); - - b.Property("Role") - .HasColumnType("integer") - .HasColumnName("role"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_chat_members"); - - b.HasAlternateKey("ChatRoomId", "AccountId") - .HasName("ak_chat_members_chat_room_id_account_id"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_chat_members_account_id"); - - b.ToTable("chat_members", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatRoom", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_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("Description") - .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") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - b.Property("RealmId") - .HasColumnType("uuid") - .HasColumnName("realm_id"); - - b.Property("Type") - .HasColumnType("integer") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_chat_rooms"); - - b.HasIndex("BackgroundId") - .HasDatabaseName("ix_chat_rooms_background_id"); - - b.HasIndex("PictureId") - .HasDatabaseName("ix_chat_rooms_picture_id"); - - b.HasIndex("RealmId") - .HasDatabaseName("ix_chat_rooms_realm_id"); - - b.ToTable("chat_rooms", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.Message", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("ChatRoomId") - .HasColumnType("uuid") - .HasColumnName("chat_room_id"); - - b.Property("Content") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .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("EditedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("edited_at"); - - b.Property("ForwardedMessageId") - .HasColumnType("uuid") - .HasColumnName("forwarded_message_id"); - - b.Property>("MembersMentioned") - .HasColumnType("jsonb") - .HasColumnName("members_mentioned"); - - b.Property>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("Nonce") - .IsRequired() - .HasMaxLength(36) - .HasColumnType("character varying(36)") - .HasColumnName("nonce"); - - b.Property("RepliedMessageId") - .HasColumnType("uuid") - .HasColumnName("replied_message_id"); - - b.Property("SenderId") - .HasColumnType("uuid") - .HasColumnName("sender_id"); - - b.Property("Type") - .IsRequired() - .HasColumnType("text") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_chat_messages"); - - b.HasIndex("ChatRoomId") - .HasDatabaseName("ix_chat_messages_chat_room_id"); - - b.HasIndex("ForwardedMessageId") - .HasDatabaseName("ix_chat_messages_forwarded_message_id"); - - b.HasIndex("RepliedMessageId") - .HasDatabaseName("ix_chat_messages_replied_message_id"); - - b.HasIndex("SenderId") - .HasDatabaseName("ix_chat_messages_sender_id"); - - b.ToTable("chat_messages", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.MessageReaction", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("Attitude") - .HasColumnType("integer") - .HasColumnName("attitude"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("MessageId") - .HasColumnType("uuid") - .HasColumnName("message_id"); - - b.Property("SenderId") - .HasColumnType("uuid") - .HasColumnName("sender_id"); - - b.Property("Symbol") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("symbol"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_chat_reactions"); - - b.HasIndex("MessageId") - .HasDatabaseName("ix_chat_reactions_message_id"); - - b.HasIndex("SenderId") - .HasDatabaseName("ix_chat_reactions_sender_id"); - - b.ToTable("chat_reactions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.MessageReadReceipt", b => - { - b.Property("MessageId") - .HasColumnType("uuid") - .HasColumnName("message_id"); - - b.Property("SenderId") - .HasColumnType("uuid") - .HasColumnName("sender_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("MessageId", "SenderId") - .HasName("pk_chat_read_receipts"); - - b.HasIndex("SenderId") - .HasDatabaseName("ix_chat_read_receipts_sender_id"); - - b.HasIndex("MessageId", "SenderId") - .IsUnique() - .HasDatabaseName("ix_chat_read_receipts_message_id_sender_id"); - - b.ToTable("chat_read_receipts", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.RealtimeCall", 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("EndedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("ended_at"); - - b.Property("RoomId") - .HasColumnType("uuid") - .HasColumnName("room_id"); - - b.Property("SenderId") - .HasColumnType("uuid") - .HasColumnName("sender_id"); - - b.Property("Title") - .HasColumnType("text") - .HasColumnName("title"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_chat_realtime_call"); - - b.HasIndex("RoomId") - .HasDatabaseName("ix_chat_realtime_call_room_id"); - - b.HasIndex("SenderId") - .HasDatabaseName("ix_chat_realtime_call_sender_id"); - - b.ToTable("chat_realtime_call", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomApp", 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("Name") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("slug"); - - b.Property("Status") - .HasColumnType("integer") - .HasColumnName("status"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("VerifiedAs") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("verified_as"); - - b.Property("VerifiedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("verified_at"); - - b.HasKey("Id") - .HasName("pk_custom_apps"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_custom_apps_publisher_id"); - - b.ToTable("custom_apps", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomAppSecret", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AppId") - .HasColumnType("uuid") - .HasColumnName("app_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("Remarks") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("remarks"); - - b.Property("Secret") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("secret"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_custom_app_secrets"); - - b.HasIndex("AppId") - .HasDatabaseName("ix_custom_app_secrets_app_id"); - - b.ToTable("custom_app_secrets", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroup", 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.Sphere.Permission.PermissionGroupMember", 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.Sphere.Permission.PermissionNode", 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("Area") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("area"); - - 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("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", "Area", "Actor") - .HasDatabaseName("ix_permission_nodes_key_area_actor"); - - b.ToTable("permission_nodes", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.Post", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("Content") - .HasColumnType("text") - .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("Description") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("Downvotes") - .HasColumnType("integer") - .HasColumnName("downvotes"); - - b.Property("EditedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("edited_at"); - - b.Property("ForwardedPostId") - .HasColumnType("uuid") - .HasColumnName("forwarded_post_id"); - - b.Property("Language") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("language"); - - b.Property>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("PublishedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("published_at"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("RepliedPostId") - .HasColumnType("uuid") - .HasColumnName("replied_post_id"); - - b.Property("SearchVector") - .IsRequired() - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("tsvector") - .HasColumnName("search_vector") - .HasAnnotation("Npgsql:TsVectorConfig", "simple") - .HasAnnotation("Npgsql:TsVectorProperties", new[] { "Title", "Description", "Content" }); - - b.Property("ThreadedPostId") - .HasColumnType("uuid") - .HasColumnName("threaded_post_id"); - - b.Property("Title") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("title"); - - b.Property("Type") - .HasColumnType("integer") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("Upvotes") - .HasColumnType("integer") - .HasColumnName("upvotes"); - - b.Property("ViewsTotal") - .HasColumnType("integer") - .HasColumnName("views_total"); - - b.Property("ViewsUnique") - .HasColumnType("integer") - .HasColumnName("views_unique"); - - b.Property("Visibility") - .HasColumnType("integer") - .HasColumnName("visibility"); - - b.HasKey("Id") - .HasName("pk_posts"); - - b.HasIndex("ForwardedPostId") - .HasDatabaseName("ix_posts_forwarded_post_id"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_posts_publisher_id"); - - b.HasIndex("RepliedPostId") - .HasDatabaseName("ix_posts_replied_post_id"); - - b.HasIndex("SearchVector") - .HasDatabaseName("ix_posts_search_vector"); - - NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex("SearchVector"), "GIN"); - - b.HasIndex("ThreadedPostId") - .IsUnique() - .HasDatabaseName("ix_posts_threaded_post_id"); - - b.ToTable("posts", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostCategory", 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("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("name"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_categories"); - - b.ToTable("post_categories", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostCollection", 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("Description") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("name"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_collections"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_post_collections_publisher_id"); - - b.ToTable("post_collections", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostReaction", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("Attitude") - .HasColumnType("integer") - .HasColumnName("attitude"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("PostId") - .HasColumnType("uuid") - .HasColumnName("post_id"); - - b.Property("Symbol") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("symbol"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_reactions"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_post_reactions_account_id"); - - b.HasIndex("PostId") - .HasDatabaseName("ix_post_reactions_post_id"); - - b.ToTable("post_reactions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostTag", 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("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("name"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_tags"); - - b.ToTable("post_tags", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.Publisher", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("BackgroundId") - .HasColumnType("character varying(32)") - .HasColumnName("background_id"); - - b.Property("Bio") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("bio"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - 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("PictureId") - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - b.Property("RealmId") - .HasColumnType("uuid") - .HasColumnName("realm_id"); - - b.Property("Type") - .HasColumnType("integer") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_publishers"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_publishers_account_id"); - - b.HasIndex("BackgroundId") - .HasDatabaseName("ix_publishers_background_id"); - - b.HasIndex("Name") - .IsUnique() - .HasDatabaseName("ix_publishers_name"); - - b.HasIndex("PictureId") - .HasDatabaseName("ix_publishers_picture_id"); - - b.HasIndex("RealmId") - .HasDatabaseName("ix_publishers_realm_id"); - - b.ToTable("publishers", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherFeature", 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("ExpiredAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expired_at"); - - b.Property("Flag") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("flag"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_publisher_features"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_publisher_features_publisher_id"); - - b.ToTable("publisher_features", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherMember", b => - { - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_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("Role") - .HasColumnType("integer") - .HasColumnName("role"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("PublisherId", "AccountId") - .HasName("pk_publisher_members"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_publisher_members_account_id"); - - b.ToTable("publisher_members", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherSubscription", 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("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("Status") - .HasColumnType("integer") - .HasColumnName("status"); - - b.Property("Tier") - .HasColumnType("integer") - .HasColumnName("tier"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_publisher_subscriptions"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_publisher_subscriptions_account_id"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_publisher_subscriptions_publisher_id"); - - b.ToTable("publisher_subscriptions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.Realm", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_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("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("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - 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("VerifiedAs") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("verified_as"); - - b.Property("VerifiedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("verified_at"); - - b.HasKey("Id") - .HasName("pk_realms"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_realms_account_id"); - - b.HasIndex("BackgroundId") - .HasDatabaseName("ix_realms_background_id"); - - b.HasIndex("PictureId") - .HasDatabaseName("ix_realms_picture_id"); - - b.HasIndex("Slug") - .IsUnique() - .HasDatabaseName("ix_realms_slug"); - - b.ToTable("realms", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.RealmMember", 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.HasIndex("AccountId") - .HasDatabaseName("ix_realm_members_account_id"); - - b.ToTable("realm_members", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.Sticker", 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("ImageId") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("image_id"); - - b.Property("PackId") - .HasColumnType("uuid") - .HasColumnName("pack_id"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_stickers"); - - b.HasIndex("ImageId") - .HasDatabaseName("ix_stickers_image_id"); - - b.HasIndex("PackId") - .HasDatabaseName("ix_stickers_pack_id"); - - b.HasIndex("Slug") - .HasDatabaseName("ix_stickers_slug"); - - b.ToTable("stickers", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.StickerPack", 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("Description") - .IsRequired() - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("Prefix") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("prefix"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_sticker_packs"); - - b.HasIndex("Prefix") - .IsUnique() - .HasDatabaseName("ix_sticker_packs_prefix"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_sticker_packs_publisher_id"); - - b.ToTable("sticker_packs", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFile", b => - { - b.Property("Id") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .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("Description") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("ExpiredAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expired_at"); - - b.Property>("FileMeta") - .HasColumnType("jsonb") - .HasColumnName("file_meta"); - - b.Property("HasCompression") - .HasColumnType("boolean") - .HasColumnName("has_compression"); - - b.Property("Hash") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("hash"); - - b.Property("MessageId") - .HasColumnType("uuid") - .HasColumnName("message_id"); - - b.Property("MimeType") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("mime_type"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("PostId") - .HasColumnType("uuid") - .HasColumnName("post_id"); - - b.Property>("SensitiveMarks") - .HasColumnType("jsonb") - .HasColumnName("sensitive_marks"); - - b.Property("Size") - .HasColumnType("bigint") - .HasColumnName("size"); - - b.Property("StorageId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("storage_id"); - - b.Property("StorageUrl") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("storage_url"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("UploadedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("uploaded_at"); - - b.Property("UploadedTo") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("uploaded_to"); - - b.Property("UsedCount") - .HasColumnType("integer") - .HasColumnName("used_count"); - - b.Property>("UserMeta") - .HasColumnType("jsonb") - .HasColumnName("user_meta"); - - b.HasKey("Id") - .HasName("pk_files"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_files_account_id"); - - b.HasIndex("MessageId") - .HasDatabaseName("ix_files_message_id"); - - b.HasIndex("PostId") - .HasDatabaseName("ix_files_post_id"); - - b.ToTable("files", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Order", 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("ExpiredAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expired_at"); - - b.Property("IssuerAppId") - .HasColumnType("uuid") - .HasColumnName("issuer_app_id"); - - b.Property("PayeeWalletId") - .HasColumnType("uuid") - .HasColumnName("payee_wallet_id"); - - 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("IssuerAppId") - .HasDatabaseName("ix_payment_orders_issuer_app_id"); - - 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.Sphere.Wallet.Transaction", 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.Sphere.Wallet.Wallet", 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.Sphere.Wallet.WalletPocket", 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("PostPostCategory", b => - { - b.Property("CategoriesId") - .HasColumnType("uuid") - .HasColumnName("categories_id"); - - b.Property("PostsId") - .HasColumnType("uuid") - .HasColumnName("posts_id"); - - b.HasKey("CategoriesId", "PostsId") - .HasName("pk_post_category_links"); - - b.HasIndex("PostsId") - .HasDatabaseName("ix_post_category_links_posts_id"); - - b.ToTable("post_category_links", (string)null); - }); - - modelBuilder.Entity("PostPostCollection", b => - { - b.Property("CollectionsId") - .HasColumnType("uuid") - .HasColumnName("collections_id"); - - b.Property("PostsId") - .HasColumnType("uuid") - .HasColumnName("posts_id"); - - b.HasKey("CollectionsId", "PostsId") - .HasName("pk_post_collection_links"); - - b.HasIndex("PostsId") - .HasDatabaseName("ix_post_collection_links_posts_id"); - - b.ToTable("post_collection_links", (string)null); - }); - - modelBuilder.Entity("PostPostTag", b => - { - b.Property("PostsId") - .HasColumnType("uuid") - .HasColumnName("posts_id"); - - b.Property("TagsId") - .HasColumnType("uuid") - .HasColumnName("tags_id"); - - b.HasKey("PostsId", "TagsId") - .HasName("pk_post_tag_links"); - - b.HasIndex("TagsId") - .HasDatabaseName("ix_post_tag_links_tags_id"); - - b.ToTable("post_tag_links", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.AccountAuthFactor", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("AuthFactors") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_auth_factors_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.AccountContact", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Contacts") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_contacts_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.ActionLog", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_action_logs_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Auth.Session", "Session") - .WithMany() - .HasForeignKey("SessionId") - .HasConstraintName("fk_action_logs_auth_sessions_session_id"); - - b.Navigation("Account"); - - b.Navigation("Session"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Badge", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Badges") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_badges_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.CheckInResult", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_check_in_results_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.MagicSpell", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .HasConstraintName("fk_magic_spells_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Notification", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_notifications_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.NotificationPushSubscription", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_notification_push_subscriptions_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Profile", b => - { - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "Background") - .WithMany() - .HasForeignKey("BackgroundId") - .HasConstraintName("fk_account_profiles_files_background_id"); - - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithOne("Profile") - .HasForeignKey("DysonNetwork.Sphere.Account.Profile", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_profiles_accounts_id"); - - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "Picture") - .WithMany() - .HasForeignKey("PictureId") - .HasConstraintName("fk_account_profiles_files_picture_id"); - - b.Navigation("Account"); - - b.Navigation("Background"); - - b.Navigation("Picture"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Relationship", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("OutgoingRelationships") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_relationships_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Account.Account", "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.Sphere.Account.Status", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_statuses_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Activity.Activity", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_activities_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Challenge", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Challenges") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_challenges_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Session", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Sessions") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_sessions_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Auth.Challenge", "Challenge") - .WithMany() - .HasForeignKey("ChallengeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_sessions_auth_challenges_challenge_id"); - - b.Navigation("Account"); - - b.Navigation("Challenge"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatMember", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_members_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatRoom", "ChatRoom") - .WithMany("Members") - .HasForeignKey("ChatRoomId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_members_chat_rooms_chat_room_id"); - - b.Navigation("Account"); - - b.Navigation("ChatRoom"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatRoom", b => - { - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "Background") - .WithMany() - .HasForeignKey("BackgroundId") - .HasConstraintName("fk_chat_rooms_files_background_id"); - - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "Picture") - .WithMany() - .HasForeignKey("PictureId") - .HasConstraintName("fk_chat_rooms_files_picture_id"); - - b.HasOne("DysonNetwork.Sphere.Realm.Realm", "Realm") - .WithMany("ChatRooms") - .HasForeignKey("RealmId") - .HasConstraintName("fk_chat_rooms_realms_realm_id"); - - b.Navigation("Background"); - - b.Navigation("Picture"); - - b.Navigation("Realm"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.Message", b => - { - b.HasOne("DysonNetwork.Sphere.Chat.ChatRoom", "ChatRoom") - .WithMany() - .HasForeignKey("ChatRoomId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_messages_chat_rooms_chat_room_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.Message", "ForwardedMessage") - .WithMany() - .HasForeignKey("ForwardedMessageId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_chat_messages_chat_messages_forwarded_message_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.Message", "RepliedMessage") - .WithMany() - .HasForeignKey("RepliedMessageId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_chat_messages_chat_messages_replied_message_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatMember", "Sender") - .WithMany() - .HasForeignKey("SenderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_messages_chat_members_sender_id"); - - b.Navigation("ChatRoom"); - - b.Navigation("ForwardedMessage"); - - b.Navigation("RepliedMessage"); - - b.Navigation("Sender"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.MessageReaction", b => - { - b.HasOne("DysonNetwork.Sphere.Chat.Message", "Message") - .WithMany("Reactions") - .HasForeignKey("MessageId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_reactions_chat_messages_message_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatMember", "Sender") - .WithMany() - .HasForeignKey("SenderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_reactions_chat_members_sender_id"); - - b.Navigation("Message"); - - b.Navigation("Sender"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.MessageReadReceipt", b => - { - b.HasOne("DysonNetwork.Sphere.Chat.Message", "Message") - .WithMany("Statuses") - .HasForeignKey("MessageId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_read_receipts_chat_messages_message_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatMember", "Sender") - .WithMany() - .HasForeignKey("SenderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_read_receipts_chat_members_sender_id"); - - b.Navigation("Message"); - - b.Navigation("Sender"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.RealtimeCall", b => - { - b.HasOne("DysonNetwork.Sphere.Chat.ChatRoom", "Room") - .WithMany() - .HasForeignKey("RoomId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_realtime_call_chat_rooms_room_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatMember", "Sender") - .WithMany() - .HasForeignKey("SenderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_realtime_call_chat_members_sender_id"); - - b.Navigation("Room"); - - b.Navigation("Sender"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomApp", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Developer") - .WithMany() - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_custom_apps_publishers_publisher_id"); - - b.Navigation("Developer"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomAppSecret", b => - { - b.HasOne("DysonNetwork.Sphere.Developer.CustomApp", "App") - .WithMany() - .HasForeignKey("AppId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_custom_app_secrets_custom_apps_app_id"); - - b.Navigation("App"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroupMember", b => - { - b.HasOne("DysonNetwork.Sphere.Permission.PermissionGroup", "Group") - .WithMany("Members") - .HasForeignKey("GroupId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_permission_group_members_permission_groups_group_id"); - - b.Navigation("Group"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionNode", b => - { - b.HasOne("DysonNetwork.Sphere.Permission.PermissionGroup", "Group") - .WithMany("Nodes") - .HasForeignKey("GroupId") - .HasConstraintName("fk_permission_nodes_permission_groups_group_id"); - - b.Navigation("Group"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.Post", b => - { - b.HasOne("DysonNetwork.Sphere.Post.Post", "ForwardedPost") - .WithMany() - .HasForeignKey("ForwardedPostId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_posts_posts_forwarded_post_id"); - - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Posts") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_posts_publishers_publisher_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", "RepliedPost") - .WithMany() - .HasForeignKey("RepliedPostId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_posts_posts_replied_post_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", "ThreadedPost") - .WithOne() - .HasForeignKey("DysonNetwork.Sphere.Post.Post", "ThreadedPostId") - .HasConstraintName("fk_posts_posts_threaded_post_id"); - - b.Navigation("ForwardedPost"); - - b.Navigation("Publisher"); - - b.Navigation("RepliedPost"); - - b.Navigation("ThreadedPost"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostCollection", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Collections") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_collections_publishers_publisher_id"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostReaction", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_reactions_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", "Post") - .WithMany("Reactions") - .HasForeignKey("PostId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_reactions_posts_post_id"); - - b.Navigation("Account"); - - b.Navigation("Post"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.Publisher", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .HasConstraintName("fk_publishers_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "Background") - .WithMany() - .HasForeignKey("BackgroundId") - .HasConstraintName("fk_publishers_files_background_id"); - - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "Picture") - .WithMany() - .HasForeignKey("PictureId") - .HasConstraintName("fk_publishers_files_picture_id"); - - b.HasOne("DysonNetwork.Sphere.Realm.Realm", "Realm") - .WithMany() - .HasForeignKey("RealmId") - .HasConstraintName("fk_publishers_realms_realm_id"); - - b.Navigation("Account"); - - b.Navigation("Background"); - - b.Navigation("Picture"); - - b.Navigation("Realm"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherFeature", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany() - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_features_publishers_publisher_id"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherMember", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_members_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Members") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_members_publishers_publisher_id"); - - b.Navigation("Account"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherSubscription", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_subscriptions_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Subscriptions") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_subscriptions_publishers_publisher_id"); - - b.Navigation("Account"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.Realm", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_realms_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "Background") - .WithMany() - .HasForeignKey("BackgroundId") - .HasConstraintName("fk_realms_files_background_id"); - - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "Picture") - .WithMany() - .HasForeignKey("PictureId") - .HasConstraintName("fk_realms_files_picture_id"); - - b.Navigation("Account"); - - b.Navigation("Background"); - - b.Navigation("Picture"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.RealmMember", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_realm_members_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Realm.Realm", "Realm") - .WithMany("Members") - .HasForeignKey("RealmId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_realm_members_realms_realm_id"); - - b.Navigation("Account"); - - b.Navigation("Realm"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.Sticker", b => - { - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "Image") - .WithMany() - .HasForeignKey("ImageId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_stickers_files_image_id"); - - b.HasOne("DysonNetwork.Sphere.Sticker.StickerPack", "Pack") - .WithMany() - .HasForeignKey("PackId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_stickers_sticker_packs_pack_id"); - - b.Navigation("Image"); - - b.Navigation("Pack"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.StickerPack", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany() - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_sticker_packs_publishers_publisher_id"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFile", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_files_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.Message", null) - .WithMany("Attachments") - .HasForeignKey("MessageId") - .HasConstraintName("fk_files_chat_messages_message_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany("Attachments") - .HasForeignKey("PostId") - .HasConstraintName("fk_files_posts_post_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Order", b => - { - b.HasOne("DysonNetwork.Sphere.Developer.CustomApp", "IssuerApp") - .WithMany() - .HasForeignKey("IssuerAppId") - .HasConstraintName("fk_payment_orders_custom_apps_issuer_app_id"); - - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "PayeeWallet") - .WithMany() - .HasForeignKey("PayeeWalletId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_payment_orders_wallets_payee_wallet_id"); - - b.HasOne("DysonNetwork.Sphere.Wallet.Transaction", "Transaction") - .WithMany() - .HasForeignKey("TransactionId") - .HasConstraintName("fk_payment_orders_payment_transactions_transaction_id"); - - b.Navigation("IssuerApp"); - - b.Navigation("PayeeWallet"); - - b.Navigation("Transaction"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Transaction", b => - { - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "PayeeWallet") - .WithMany() - .HasForeignKey("PayeeWalletId") - .HasConstraintName("fk_payment_transactions_wallets_payee_wallet_id"); - - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "PayerWallet") - .WithMany() - .HasForeignKey("PayerWalletId") - .HasConstraintName("fk_payment_transactions_wallets_payer_wallet_id"); - - b.Navigation("PayeeWallet"); - - b.Navigation("PayerWallet"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Wallet", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_wallets_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.WalletPocket", b => - { - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "Wallet") - .WithMany("Pockets") - .HasForeignKey("WalletId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_wallet_pockets_wallets_wallet_id"); - - b.Navigation("Wallet"); - }); - - modelBuilder.Entity("PostPostCategory", b => - { - b.HasOne("DysonNetwork.Sphere.Post.PostCategory", null) - .WithMany() - .HasForeignKey("CategoriesId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_category_links_post_categories_categories_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany() - .HasForeignKey("PostsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_category_links_posts_posts_id"); - }); - - modelBuilder.Entity("PostPostCollection", b => - { - b.HasOne("DysonNetwork.Sphere.Post.PostCollection", null) - .WithMany() - .HasForeignKey("CollectionsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_collection_links_post_collections_collections_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany() - .HasForeignKey("PostsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_collection_links_posts_posts_id"); - }); - - modelBuilder.Entity("PostPostTag", b => - { - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany() - .HasForeignKey("PostsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_tag_links_posts_posts_id"); - - b.HasOne("DysonNetwork.Sphere.Post.PostTag", null) - .WithMany() - .HasForeignKey("TagsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_tag_links_post_tags_tags_id"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Account", b => - { - b.Navigation("AuthFactors"); - - b.Navigation("Badges"); - - b.Navigation("Challenges"); - - b.Navigation("Contacts"); - - b.Navigation("IncomingRelationships"); - - b.Navigation("OutgoingRelationships"); - - b.Navigation("Profile") - .IsRequired(); - - b.Navigation("Sessions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatRoom", b => - { - b.Navigation("Members"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.Message", b => - { - b.Navigation("Attachments"); - - b.Navigation("Reactions"); - - b.Navigation("Statuses"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroup", b => - { - b.Navigation("Members"); - - b.Navigation("Nodes"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.Post", b => - { - b.Navigation("Attachments"); - - b.Navigation("Reactions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.Publisher", b => - { - b.Navigation("Collections"); - - b.Navigation("Members"); - - b.Navigation("Posts"); - - b.Navigation("Subscriptions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.Realm", b => - { - b.Navigation("ChatRooms"); - - b.Navigation("Members"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Wallet", b => - { - b.Navigation("Pockets"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/DysonNetwork.Sphere/Migrations/20250521142845_EnrichAccountProfile.cs b/DysonNetwork.Sphere/Migrations/20250521142845_EnrichAccountProfile.cs deleted file mode 100644 index 207965b..0000000 --- a/DysonNetwork.Sphere/Migrations/20250521142845_EnrichAccountProfile.cs +++ /dev/null @@ -1,61 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; -using NodaTime; - -#nullable disable - -namespace DysonNetwork.Sphere.Migrations -{ - /// - public partial class EnrichAccountProfile : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "birthday", - table: "account_profiles", - type: "timestamp with time zone", - nullable: true); - - migrationBuilder.AddColumn( - name: "gender", - table: "account_profiles", - type: "character varying(1024)", - maxLength: 1024, - nullable: true); - - migrationBuilder.AddColumn( - name: "last_seen_at", - table: "account_profiles", - type: "timestamp with time zone", - nullable: true); - - migrationBuilder.AddColumn( - name: "pronouns", - table: "account_profiles", - type: "character varying(1024)", - maxLength: 1024, - nullable: true); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "birthday", - table: "account_profiles"); - - migrationBuilder.DropColumn( - name: "gender", - table: "account_profiles"); - - migrationBuilder.DropColumn( - name: "last_seen_at", - table: "account_profiles"); - - migrationBuilder.DropColumn( - name: "pronouns", - table: "account_profiles"); - } - } -} diff --git a/DysonNetwork.Sphere/Migrations/20250521181143_FixProfileRelationship.Designer.cs b/DysonNetwork.Sphere/Migrations/20250521181143_FixProfileRelationship.Designer.cs deleted file mode 100644 index 3ca5e4d..0000000 --- a/DysonNetwork.Sphere/Migrations/20250521181143_FixProfileRelationship.Designer.cs +++ /dev/null @@ -1,3449 +0,0 @@ -// -using System; -using System.Collections.Generic; -using System.Text.Json; -using DysonNetwork.Sphere; -using DysonNetwork.Sphere.Account; -using DysonNetwork.Sphere.Storage; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using NetTopologySuite.Geometries; -using NodaTime; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; -using NpgsqlTypes; - -#nullable disable - -namespace DysonNetwork.Sphere.Migrations -{ - [DbContext(typeof(AppDatabase))] - [Migration("20250521181143_FixProfileRelationship")] - partial class FixProfileRelationship - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "9.0.3") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "postgis"); - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Account", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("ActivatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("activated_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("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("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.Sphere.Account.AccountAuthFactor", 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("Secret") - .HasMaxLength(8196) - .HasColumnType("character varying(8196)") - .HasColumnName("secret"); - - 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.Sphere.Account.AccountContact", 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("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.Sphere.Account.ActionLog", 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("geometry") - .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.HasIndex("SessionId") - .HasDatabaseName("ix_action_logs_session_id"); - - b.ToTable("action_logs", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Badge", 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("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.Sphere.Account.CheckInResult", 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("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.Sphere.Account.MagicSpell", 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.Sphere.Account.Notification", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("Content") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .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>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("Priority") - .HasColumnType("integer") - .HasColumnName("priority"); - - b.Property("Subtitle") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("subtitle"); - - b.Property("Title") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("title"); - - b.Property("Topic") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("topic"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("ViewedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("viewed_at"); - - b.HasKey("Id") - .HasName("pk_notifications"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_notifications_account_id"); - - b.ToTable("notifications", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.NotificationPushSubscription", 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(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("device_id"); - - b.Property("DeviceToken") - .IsRequired() - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("device_token"); - - b.Property("LastUsedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_used_at"); - - b.Property("Provider") - .HasColumnType("integer") - .HasColumnName("provider"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_notification_push_subscriptions"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_notification_push_subscriptions_account_id"); - - b.HasIndex("DeviceId") - .IsUnique() - .HasDatabaseName("ix_notification_push_subscriptions_device_id"); - - b.HasIndex("DeviceToken") - .IsUnique() - .HasDatabaseName("ix_notification_push_subscriptions_device_token"); - - b.ToTable("notification_push_subscriptions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Profile", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_id"); - - 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("MiddleName") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("middle_name"); - - b.Property("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - b.Property("Pronouns") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("pronouns"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_account_profiles"); - - b.HasIndex("AccountId") - .IsUnique() - .HasDatabaseName("ix_account_profiles_account_id"); - - b.HasIndex("BackgroundId") - .HasDatabaseName("ix_account_profiles_background_id"); - - b.HasIndex("PictureId") - .HasDatabaseName("ix_account_profiles_picture_id"); - - b.ToTable("account_profiles", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Relationship", 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("integer") - .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.Sphere.Account.Status", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - 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("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("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.Sphere.Activity.Activity", 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>("Meta") - .IsRequired() - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("ResourceIdentifier") - .IsRequired() - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("resource_identifier"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property>("UsersVisible") - .IsRequired() - .HasColumnType("jsonb") - .HasColumnName("users_visible"); - - b.Property("Visibility") - .HasColumnType("integer") - .HasColumnName("visibility"); - - b.HasKey("Id") - .HasName("pk_activities"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_activities_account_id"); - - b.ToTable("activities", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Challenge", 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") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("device_id"); - - 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("geometry") - .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("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_challenges"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_auth_challenges_account_id"); - - b.ToTable("auth_challenges", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Session", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("ChallengeId") - .HasColumnType("uuid") - .HasColumnName("challenge_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("Label") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("label"); - - b.Property("LastGrantedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_granted_at"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - 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.ToTable("auth_sessions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatMember", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("ChatRoomId") - .HasColumnType("uuid") - .HasColumnName("chat_room_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("IsBot") - .HasColumnType("boolean") - .HasColumnName("is_bot"); - - b.Property("JoinedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("joined_at"); - - b.Property("LeaveAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("leave_at"); - - b.Property("Nick") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("nick"); - - b.Property("Notify") - .HasColumnType("integer") - .HasColumnName("notify"); - - b.Property("Role") - .HasColumnType("integer") - .HasColumnName("role"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_chat_members"); - - b.HasAlternateKey("ChatRoomId", "AccountId") - .HasName("ak_chat_members_chat_room_id_account_id"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_chat_members_account_id"); - - b.ToTable("chat_members", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatRoom", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_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("Description") - .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") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - b.Property("RealmId") - .HasColumnType("uuid") - .HasColumnName("realm_id"); - - b.Property("Type") - .HasColumnType("integer") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_chat_rooms"); - - b.HasIndex("BackgroundId") - .HasDatabaseName("ix_chat_rooms_background_id"); - - b.HasIndex("PictureId") - .HasDatabaseName("ix_chat_rooms_picture_id"); - - b.HasIndex("RealmId") - .HasDatabaseName("ix_chat_rooms_realm_id"); - - b.ToTable("chat_rooms", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.Message", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("ChatRoomId") - .HasColumnType("uuid") - .HasColumnName("chat_room_id"); - - b.Property("Content") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .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("EditedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("edited_at"); - - b.Property("ForwardedMessageId") - .HasColumnType("uuid") - .HasColumnName("forwarded_message_id"); - - b.Property>("MembersMentioned") - .HasColumnType("jsonb") - .HasColumnName("members_mentioned"); - - b.Property>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("Nonce") - .IsRequired() - .HasMaxLength(36) - .HasColumnType("character varying(36)") - .HasColumnName("nonce"); - - b.Property("RepliedMessageId") - .HasColumnType("uuid") - .HasColumnName("replied_message_id"); - - b.Property("SenderId") - .HasColumnType("uuid") - .HasColumnName("sender_id"); - - b.Property("Type") - .IsRequired() - .HasColumnType("text") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_chat_messages"); - - b.HasIndex("ChatRoomId") - .HasDatabaseName("ix_chat_messages_chat_room_id"); - - b.HasIndex("ForwardedMessageId") - .HasDatabaseName("ix_chat_messages_forwarded_message_id"); - - b.HasIndex("RepliedMessageId") - .HasDatabaseName("ix_chat_messages_replied_message_id"); - - b.HasIndex("SenderId") - .HasDatabaseName("ix_chat_messages_sender_id"); - - b.ToTable("chat_messages", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.MessageReaction", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("Attitude") - .HasColumnType("integer") - .HasColumnName("attitude"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("MessageId") - .HasColumnType("uuid") - .HasColumnName("message_id"); - - b.Property("SenderId") - .HasColumnType("uuid") - .HasColumnName("sender_id"); - - b.Property("Symbol") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("symbol"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_chat_reactions"); - - b.HasIndex("MessageId") - .HasDatabaseName("ix_chat_reactions_message_id"); - - b.HasIndex("SenderId") - .HasDatabaseName("ix_chat_reactions_sender_id"); - - b.ToTable("chat_reactions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.MessageReadReceipt", b => - { - b.Property("MessageId") - .HasColumnType("uuid") - .HasColumnName("message_id"); - - b.Property("SenderId") - .HasColumnType("uuid") - .HasColumnName("sender_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("MessageId", "SenderId") - .HasName("pk_chat_read_receipts"); - - b.HasIndex("SenderId") - .HasDatabaseName("ix_chat_read_receipts_sender_id"); - - b.HasIndex("MessageId", "SenderId") - .IsUnique() - .HasDatabaseName("ix_chat_read_receipts_message_id_sender_id"); - - b.ToTable("chat_read_receipts", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.RealtimeCall", 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("EndedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("ended_at"); - - b.Property("RoomId") - .HasColumnType("uuid") - .HasColumnName("room_id"); - - b.Property("SenderId") - .HasColumnType("uuid") - .HasColumnName("sender_id"); - - b.Property("Title") - .HasColumnType("text") - .HasColumnName("title"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_chat_realtime_call"); - - b.HasIndex("RoomId") - .HasDatabaseName("ix_chat_realtime_call_room_id"); - - b.HasIndex("SenderId") - .HasDatabaseName("ix_chat_realtime_call_sender_id"); - - b.ToTable("chat_realtime_call", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomApp", 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("Name") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("slug"); - - b.Property("Status") - .HasColumnType("integer") - .HasColumnName("status"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("VerifiedAs") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("verified_as"); - - b.Property("VerifiedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("verified_at"); - - b.HasKey("Id") - .HasName("pk_custom_apps"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_custom_apps_publisher_id"); - - b.ToTable("custom_apps", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomAppSecret", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AppId") - .HasColumnType("uuid") - .HasColumnName("app_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("Remarks") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("remarks"); - - b.Property("Secret") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("secret"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_custom_app_secrets"); - - b.HasIndex("AppId") - .HasDatabaseName("ix_custom_app_secrets_app_id"); - - b.ToTable("custom_app_secrets", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroup", 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.Sphere.Permission.PermissionGroupMember", 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.Sphere.Permission.PermissionNode", 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("Area") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("area"); - - 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("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", "Area", "Actor") - .HasDatabaseName("ix_permission_nodes_key_area_actor"); - - b.ToTable("permission_nodes", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.Post", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("Content") - .HasColumnType("text") - .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("Description") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("Downvotes") - .HasColumnType("integer") - .HasColumnName("downvotes"); - - b.Property("EditedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("edited_at"); - - b.Property("ForwardedPostId") - .HasColumnType("uuid") - .HasColumnName("forwarded_post_id"); - - b.Property("Language") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("language"); - - b.Property>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("PublishedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("published_at"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("RepliedPostId") - .HasColumnType("uuid") - .HasColumnName("replied_post_id"); - - b.Property("SearchVector") - .IsRequired() - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("tsvector") - .HasColumnName("search_vector") - .HasAnnotation("Npgsql:TsVectorConfig", "simple") - .HasAnnotation("Npgsql:TsVectorProperties", new[] { "Title", "Description", "Content" }); - - b.Property("ThreadedPostId") - .HasColumnType("uuid") - .HasColumnName("threaded_post_id"); - - b.Property("Title") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("title"); - - b.Property("Type") - .HasColumnType("integer") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("Upvotes") - .HasColumnType("integer") - .HasColumnName("upvotes"); - - b.Property("ViewsTotal") - .HasColumnType("integer") - .HasColumnName("views_total"); - - b.Property("ViewsUnique") - .HasColumnType("integer") - .HasColumnName("views_unique"); - - b.Property("Visibility") - .HasColumnType("integer") - .HasColumnName("visibility"); - - b.HasKey("Id") - .HasName("pk_posts"); - - b.HasIndex("ForwardedPostId") - .HasDatabaseName("ix_posts_forwarded_post_id"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_posts_publisher_id"); - - b.HasIndex("RepliedPostId") - .HasDatabaseName("ix_posts_replied_post_id"); - - b.HasIndex("SearchVector") - .HasDatabaseName("ix_posts_search_vector"); - - NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex("SearchVector"), "GIN"); - - b.HasIndex("ThreadedPostId") - .IsUnique() - .HasDatabaseName("ix_posts_threaded_post_id"); - - b.ToTable("posts", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostCategory", 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("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("name"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_categories"); - - b.ToTable("post_categories", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostCollection", 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("Description") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("name"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_collections"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_post_collections_publisher_id"); - - b.ToTable("post_collections", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostReaction", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("Attitude") - .HasColumnType("integer") - .HasColumnName("attitude"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("PostId") - .HasColumnType("uuid") - .HasColumnName("post_id"); - - b.Property("Symbol") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("symbol"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_reactions"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_post_reactions_account_id"); - - b.HasIndex("PostId") - .HasDatabaseName("ix_post_reactions_post_id"); - - b.ToTable("post_reactions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostTag", 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("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("name"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_tags"); - - b.ToTable("post_tags", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.Publisher", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("BackgroundId") - .HasColumnType("character varying(32)") - .HasColumnName("background_id"); - - b.Property("Bio") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("bio"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - 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("PictureId") - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - b.Property("RealmId") - .HasColumnType("uuid") - .HasColumnName("realm_id"); - - b.Property("Type") - .HasColumnType("integer") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_publishers"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_publishers_account_id"); - - b.HasIndex("BackgroundId") - .HasDatabaseName("ix_publishers_background_id"); - - b.HasIndex("Name") - .IsUnique() - .HasDatabaseName("ix_publishers_name"); - - b.HasIndex("PictureId") - .HasDatabaseName("ix_publishers_picture_id"); - - b.HasIndex("RealmId") - .HasDatabaseName("ix_publishers_realm_id"); - - b.ToTable("publishers", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherFeature", 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("ExpiredAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expired_at"); - - b.Property("Flag") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("flag"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_publisher_features"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_publisher_features_publisher_id"); - - b.ToTable("publisher_features", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherMember", b => - { - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_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("Role") - .HasColumnType("integer") - .HasColumnName("role"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("PublisherId", "AccountId") - .HasName("pk_publisher_members"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_publisher_members_account_id"); - - b.ToTable("publisher_members", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherSubscription", 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("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("Status") - .HasColumnType("integer") - .HasColumnName("status"); - - b.Property("Tier") - .HasColumnType("integer") - .HasColumnName("tier"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_publisher_subscriptions"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_publisher_subscriptions_account_id"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_publisher_subscriptions_publisher_id"); - - b.ToTable("publisher_subscriptions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.Realm", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_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("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("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - 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("VerifiedAs") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("verified_as"); - - b.Property("VerifiedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("verified_at"); - - b.HasKey("Id") - .HasName("pk_realms"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_realms_account_id"); - - b.HasIndex("BackgroundId") - .HasDatabaseName("ix_realms_background_id"); - - b.HasIndex("PictureId") - .HasDatabaseName("ix_realms_picture_id"); - - b.HasIndex("Slug") - .IsUnique() - .HasDatabaseName("ix_realms_slug"); - - b.ToTable("realms", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.RealmMember", 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.HasIndex("AccountId") - .HasDatabaseName("ix_realm_members_account_id"); - - b.ToTable("realm_members", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.Sticker", 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("ImageId") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("image_id"); - - b.Property("PackId") - .HasColumnType("uuid") - .HasColumnName("pack_id"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_stickers"); - - b.HasIndex("ImageId") - .HasDatabaseName("ix_stickers_image_id"); - - b.HasIndex("PackId") - .HasDatabaseName("ix_stickers_pack_id"); - - b.HasIndex("Slug") - .HasDatabaseName("ix_stickers_slug"); - - b.ToTable("stickers", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.StickerPack", 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("Description") - .IsRequired() - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("Prefix") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("prefix"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_sticker_packs"); - - b.HasIndex("Prefix") - .IsUnique() - .HasDatabaseName("ix_sticker_packs_prefix"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_sticker_packs_publisher_id"); - - b.ToTable("sticker_packs", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFile", b => - { - b.Property("Id") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .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("Description") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("ExpiredAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expired_at"); - - b.Property>("FileMeta") - .HasColumnType("jsonb") - .HasColumnName("file_meta"); - - b.Property("HasCompression") - .HasColumnType("boolean") - .HasColumnName("has_compression"); - - b.Property("Hash") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("hash"); - - b.Property("MessageId") - .HasColumnType("uuid") - .HasColumnName("message_id"); - - b.Property("MimeType") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("mime_type"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("PostId") - .HasColumnType("uuid") - .HasColumnName("post_id"); - - b.Property>("SensitiveMarks") - .HasColumnType("jsonb") - .HasColumnName("sensitive_marks"); - - b.Property("Size") - .HasColumnType("bigint") - .HasColumnName("size"); - - b.Property("StorageId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("storage_id"); - - b.Property("StorageUrl") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("storage_url"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("UploadedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("uploaded_at"); - - b.Property("UploadedTo") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("uploaded_to"); - - b.Property("UsedCount") - .HasColumnType("integer") - .HasColumnName("used_count"); - - b.Property>("UserMeta") - .HasColumnType("jsonb") - .HasColumnName("user_meta"); - - b.HasKey("Id") - .HasName("pk_files"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_files_account_id"); - - b.HasIndex("MessageId") - .HasDatabaseName("ix_files_message_id"); - - b.HasIndex("PostId") - .HasDatabaseName("ix_files_post_id"); - - b.ToTable("files", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Order", 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("ExpiredAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expired_at"); - - b.Property("IssuerAppId") - .HasColumnType("uuid") - .HasColumnName("issuer_app_id"); - - b.Property("PayeeWalletId") - .HasColumnType("uuid") - .HasColumnName("payee_wallet_id"); - - 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("IssuerAppId") - .HasDatabaseName("ix_payment_orders_issuer_app_id"); - - 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.Sphere.Wallet.Transaction", 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.Sphere.Wallet.Wallet", 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.Sphere.Wallet.WalletPocket", 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("PostPostCategory", b => - { - b.Property("CategoriesId") - .HasColumnType("uuid") - .HasColumnName("categories_id"); - - b.Property("PostsId") - .HasColumnType("uuid") - .HasColumnName("posts_id"); - - b.HasKey("CategoriesId", "PostsId") - .HasName("pk_post_category_links"); - - b.HasIndex("PostsId") - .HasDatabaseName("ix_post_category_links_posts_id"); - - b.ToTable("post_category_links", (string)null); - }); - - modelBuilder.Entity("PostPostCollection", b => - { - b.Property("CollectionsId") - .HasColumnType("uuid") - .HasColumnName("collections_id"); - - b.Property("PostsId") - .HasColumnType("uuid") - .HasColumnName("posts_id"); - - b.HasKey("CollectionsId", "PostsId") - .HasName("pk_post_collection_links"); - - b.HasIndex("PostsId") - .HasDatabaseName("ix_post_collection_links_posts_id"); - - b.ToTable("post_collection_links", (string)null); - }); - - modelBuilder.Entity("PostPostTag", b => - { - b.Property("PostsId") - .HasColumnType("uuid") - .HasColumnName("posts_id"); - - b.Property("TagsId") - .HasColumnType("uuid") - .HasColumnName("tags_id"); - - b.HasKey("PostsId", "TagsId") - .HasName("pk_post_tag_links"); - - b.HasIndex("TagsId") - .HasDatabaseName("ix_post_tag_links_tags_id"); - - b.ToTable("post_tag_links", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.AccountAuthFactor", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("AuthFactors") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_auth_factors_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.AccountContact", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Contacts") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_contacts_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.ActionLog", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_action_logs_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Auth.Session", "Session") - .WithMany() - .HasForeignKey("SessionId") - .HasConstraintName("fk_action_logs_auth_sessions_session_id"); - - b.Navigation("Account"); - - b.Navigation("Session"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Badge", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Badges") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_badges_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.CheckInResult", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_check_in_results_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.MagicSpell", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .HasConstraintName("fk_magic_spells_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Notification", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_notifications_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.NotificationPushSubscription", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_notification_push_subscriptions_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Profile", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithOne("Profile") - .HasForeignKey("DysonNetwork.Sphere.Account.Profile", "AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_profiles_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "Background") - .WithMany() - .HasForeignKey("BackgroundId") - .HasConstraintName("fk_account_profiles_files_background_id"); - - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "Picture") - .WithMany() - .HasForeignKey("PictureId") - .HasConstraintName("fk_account_profiles_files_picture_id"); - - b.Navigation("Account"); - - b.Navigation("Background"); - - b.Navigation("Picture"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Relationship", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("OutgoingRelationships") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_relationships_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Account.Account", "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.Sphere.Account.Status", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_statuses_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Activity.Activity", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_activities_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Challenge", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Challenges") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_challenges_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Session", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Sessions") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_sessions_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Auth.Challenge", "Challenge") - .WithMany() - .HasForeignKey("ChallengeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_sessions_auth_challenges_challenge_id"); - - b.Navigation("Account"); - - b.Navigation("Challenge"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatMember", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_members_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatRoom", "ChatRoom") - .WithMany("Members") - .HasForeignKey("ChatRoomId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_members_chat_rooms_chat_room_id"); - - b.Navigation("Account"); - - b.Navigation("ChatRoom"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatRoom", b => - { - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "Background") - .WithMany() - .HasForeignKey("BackgroundId") - .HasConstraintName("fk_chat_rooms_files_background_id"); - - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "Picture") - .WithMany() - .HasForeignKey("PictureId") - .HasConstraintName("fk_chat_rooms_files_picture_id"); - - b.HasOne("DysonNetwork.Sphere.Realm.Realm", "Realm") - .WithMany("ChatRooms") - .HasForeignKey("RealmId") - .HasConstraintName("fk_chat_rooms_realms_realm_id"); - - b.Navigation("Background"); - - b.Navigation("Picture"); - - b.Navigation("Realm"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.Message", b => - { - b.HasOne("DysonNetwork.Sphere.Chat.ChatRoom", "ChatRoom") - .WithMany() - .HasForeignKey("ChatRoomId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_messages_chat_rooms_chat_room_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.Message", "ForwardedMessage") - .WithMany() - .HasForeignKey("ForwardedMessageId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_chat_messages_chat_messages_forwarded_message_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.Message", "RepliedMessage") - .WithMany() - .HasForeignKey("RepliedMessageId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_chat_messages_chat_messages_replied_message_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatMember", "Sender") - .WithMany() - .HasForeignKey("SenderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_messages_chat_members_sender_id"); - - b.Navigation("ChatRoom"); - - b.Navigation("ForwardedMessage"); - - b.Navigation("RepliedMessage"); - - b.Navigation("Sender"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.MessageReaction", b => - { - b.HasOne("DysonNetwork.Sphere.Chat.Message", "Message") - .WithMany("Reactions") - .HasForeignKey("MessageId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_reactions_chat_messages_message_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatMember", "Sender") - .WithMany() - .HasForeignKey("SenderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_reactions_chat_members_sender_id"); - - b.Navigation("Message"); - - b.Navigation("Sender"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.MessageReadReceipt", b => - { - b.HasOne("DysonNetwork.Sphere.Chat.Message", "Message") - .WithMany("Statuses") - .HasForeignKey("MessageId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_read_receipts_chat_messages_message_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatMember", "Sender") - .WithMany() - .HasForeignKey("SenderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_read_receipts_chat_members_sender_id"); - - b.Navigation("Message"); - - b.Navigation("Sender"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.RealtimeCall", b => - { - b.HasOne("DysonNetwork.Sphere.Chat.ChatRoom", "Room") - .WithMany() - .HasForeignKey("RoomId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_realtime_call_chat_rooms_room_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatMember", "Sender") - .WithMany() - .HasForeignKey("SenderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_realtime_call_chat_members_sender_id"); - - b.Navigation("Room"); - - b.Navigation("Sender"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomApp", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Developer") - .WithMany() - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_custom_apps_publishers_publisher_id"); - - b.Navigation("Developer"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomAppSecret", b => - { - b.HasOne("DysonNetwork.Sphere.Developer.CustomApp", "App") - .WithMany() - .HasForeignKey("AppId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_custom_app_secrets_custom_apps_app_id"); - - b.Navigation("App"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroupMember", b => - { - b.HasOne("DysonNetwork.Sphere.Permission.PermissionGroup", "Group") - .WithMany("Members") - .HasForeignKey("GroupId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_permission_group_members_permission_groups_group_id"); - - b.Navigation("Group"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionNode", b => - { - b.HasOne("DysonNetwork.Sphere.Permission.PermissionGroup", "Group") - .WithMany("Nodes") - .HasForeignKey("GroupId") - .HasConstraintName("fk_permission_nodes_permission_groups_group_id"); - - b.Navigation("Group"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.Post", b => - { - b.HasOne("DysonNetwork.Sphere.Post.Post", "ForwardedPost") - .WithMany() - .HasForeignKey("ForwardedPostId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_posts_posts_forwarded_post_id"); - - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Posts") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_posts_publishers_publisher_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", "RepliedPost") - .WithMany() - .HasForeignKey("RepliedPostId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_posts_posts_replied_post_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", "ThreadedPost") - .WithOne() - .HasForeignKey("DysonNetwork.Sphere.Post.Post", "ThreadedPostId") - .HasConstraintName("fk_posts_posts_threaded_post_id"); - - b.Navigation("ForwardedPost"); - - b.Navigation("Publisher"); - - b.Navigation("RepliedPost"); - - b.Navigation("ThreadedPost"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostCollection", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Collections") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_collections_publishers_publisher_id"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostReaction", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_reactions_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", "Post") - .WithMany("Reactions") - .HasForeignKey("PostId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_reactions_posts_post_id"); - - b.Navigation("Account"); - - b.Navigation("Post"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.Publisher", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .HasConstraintName("fk_publishers_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "Background") - .WithMany() - .HasForeignKey("BackgroundId") - .HasConstraintName("fk_publishers_files_background_id"); - - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "Picture") - .WithMany() - .HasForeignKey("PictureId") - .HasConstraintName("fk_publishers_files_picture_id"); - - b.HasOne("DysonNetwork.Sphere.Realm.Realm", "Realm") - .WithMany() - .HasForeignKey("RealmId") - .HasConstraintName("fk_publishers_realms_realm_id"); - - b.Navigation("Account"); - - b.Navigation("Background"); - - b.Navigation("Picture"); - - b.Navigation("Realm"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherFeature", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany() - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_features_publishers_publisher_id"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherMember", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_members_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Members") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_members_publishers_publisher_id"); - - b.Navigation("Account"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherSubscription", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_subscriptions_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Subscriptions") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_subscriptions_publishers_publisher_id"); - - b.Navigation("Account"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.Realm", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_realms_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "Background") - .WithMany() - .HasForeignKey("BackgroundId") - .HasConstraintName("fk_realms_files_background_id"); - - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "Picture") - .WithMany() - .HasForeignKey("PictureId") - .HasConstraintName("fk_realms_files_picture_id"); - - b.Navigation("Account"); - - b.Navigation("Background"); - - b.Navigation("Picture"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.RealmMember", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_realm_members_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Realm.Realm", "Realm") - .WithMany("Members") - .HasForeignKey("RealmId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_realm_members_realms_realm_id"); - - b.Navigation("Account"); - - b.Navigation("Realm"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.Sticker", b => - { - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "Image") - .WithMany() - .HasForeignKey("ImageId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_stickers_files_image_id"); - - b.HasOne("DysonNetwork.Sphere.Sticker.StickerPack", "Pack") - .WithMany() - .HasForeignKey("PackId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_stickers_sticker_packs_pack_id"); - - b.Navigation("Image"); - - b.Navigation("Pack"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.StickerPack", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany() - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_sticker_packs_publishers_publisher_id"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFile", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_files_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.Message", null) - .WithMany("Attachments") - .HasForeignKey("MessageId") - .HasConstraintName("fk_files_chat_messages_message_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany("Attachments") - .HasForeignKey("PostId") - .HasConstraintName("fk_files_posts_post_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Order", b => - { - b.HasOne("DysonNetwork.Sphere.Developer.CustomApp", "IssuerApp") - .WithMany() - .HasForeignKey("IssuerAppId") - .HasConstraintName("fk_payment_orders_custom_apps_issuer_app_id"); - - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "PayeeWallet") - .WithMany() - .HasForeignKey("PayeeWalletId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_payment_orders_wallets_payee_wallet_id"); - - b.HasOne("DysonNetwork.Sphere.Wallet.Transaction", "Transaction") - .WithMany() - .HasForeignKey("TransactionId") - .HasConstraintName("fk_payment_orders_payment_transactions_transaction_id"); - - b.Navigation("IssuerApp"); - - b.Navigation("PayeeWallet"); - - b.Navigation("Transaction"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Transaction", b => - { - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "PayeeWallet") - .WithMany() - .HasForeignKey("PayeeWalletId") - .HasConstraintName("fk_payment_transactions_wallets_payee_wallet_id"); - - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "PayerWallet") - .WithMany() - .HasForeignKey("PayerWalletId") - .HasConstraintName("fk_payment_transactions_wallets_payer_wallet_id"); - - b.Navigation("PayeeWallet"); - - b.Navigation("PayerWallet"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Wallet", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_wallets_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.WalletPocket", b => - { - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "Wallet") - .WithMany("Pockets") - .HasForeignKey("WalletId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_wallet_pockets_wallets_wallet_id"); - - b.Navigation("Wallet"); - }); - - modelBuilder.Entity("PostPostCategory", b => - { - b.HasOne("DysonNetwork.Sphere.Post.PostCategory", null) - .WithMany() - .HasForeignKey("CategoriesId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_category_links_post_categories_categories_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany() - .HasForeignKey("PostsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_category_links_posts_posts_id"); - }); - - modelBuilder.Entity("PostPostCollection", b => - { - b.HasOne("DysonNetwork.Sphere.Post.PostCollection", null) - .WithMany() - .HasForeignKey("CollectionsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_collection_links_post_collections_collections_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany() - .HasForeignKey("PostsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_collection_links_posts_posts_id"); - }); - - modelBuilder.Entity("PostPostTag", b => - { - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany() - .HasForeignKey("PostsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_tag_links_posts_posts_id"); - - b.HasOne("DysonNetwork.Sphere.Post.PostTag", null) - .WithMany() - .HasForeignKey("TagsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_tag_links_post_tags_tags_id"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Account", b => - { - b.Navigation("AuthFactors"); - - b.Navigation("Badges"); - - b.Navigation("Challenges"); - - b.Navigation("Contacts"); - - b.Navigation("IncomingRelationships"); - - b.Navigation("OutgoingRelationships"); - - b.Navigation("Profile") - .IsRequired(); - - b.Navigation("Sessions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatRoom", b => - { - b.Navigation("Members"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.Message", b => - { - b.Navigation("Attachments"); - - b.Navigation("Reactions"); - - b.Navigation("Statuses"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroup", b => - { - b.Navigation("Members"); - - b.Navigation("Nodes"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.Post", b => - { - b.Navigation("Attachments"); - - b.Navigation("Reactions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.Publisher", b => - { - b.Navigation("Collections"); - - b.Navigation("Members"); - - b.Navigation("Posts"); - - b.Navigation("Subscriptions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.Realm", b => - { - b.Navigation("ChatRooms"); - - b.Navigation("Members"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Wallet", b => - { - b.Navigation("Pockets"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/DysonNetwork.Sphere/Migrations/20250521181143_FixProfileRelationship.cs b/DysonNetwork.Sphere/Migrations/20250521181143_FixProfileRelationship.cs deleted file mode 100644 index 5a8d814..0000000 --- a/DysonNetwork.Sphere/Migrations/20250521181143_FixProfileRelationship.cs +++ /dev/null @@ -1,52 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace DysonNetwork.Sphere.Migrations -{ - /// - public partial class FixProfileRelationship : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropForeignKey( - name: "fk_account_profiles_accounts_id", - table: "account_profiles"); - - migrationBuilder.CreateIndex( - name: "ix_account_profiles_account_id", - table: "account_profiles", - column: "account_id", - unique: true); - - migrationBuilder.AddForeignKey( - name: "fk_account_profiles_accounts_account_id", - table: "account_profiles", - column: "account_id", - principalTable: "accounts", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropForeignKey( - name: "fk_account_profiles_accounts_account_id", - table: "account_profiles"); - - migrationBuilder.DropIndex( - name: "ix_account_profiles_account_id", - table: "account_profiles"); - - migrationBuilder.AddForeignKey( - name: "fk_account_profiles_accounts_id", - table: "account_profiles", - column: "id", - principalTable: "accounts", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - } - } -} diff --git a/DysonNetwork.Sphere/Migrations/20250523172951_RefactorChatLastRead.Designer.cs b/DysonNetwork.Sphere/Migrations/20250523172951_RefactorChatLastRead.Designer.cs deleted file mode 100644 index fe36868..0000000 --- a/DysonNetwork.Sphere/Migrations/20250523172951_RefactorChatLastRead.Designer.cs +++ /dev/null @@ -1,3396 +0,0 @@ -// -using System; -using System.Collections.Generic; -using System.Text.Json; -using DysonNetwork.Sphere; -using DysonNetwork.Sphere.Account; -using DysonNetwork.Sphere.Storage; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using NetTopologySuite.Geometries; -using NodaTime; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; -using NpgsqlTypes; - -#nullable disable - -namespace DysonNetwork.Sphere.Migrations -{ - [DbContext(typeof(AppDatabase))] - [Migration("20250523172951_RefactorChatLastRead")] - partial class RefactorChatLastRead - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "9.0.3") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "postgis"); - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Account", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("ActivatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("activated_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("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("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.Sphere.Account.AccountAuthFactor", 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("Secret") - .HasMaxLength(8196) - .HasColumnType("character varying(8196)") - .HasColumnName("secret"); - - 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.Sphere.Account.AccountContact", 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("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.Sphere.Account.ActionLog", 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("geometry") - .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.HasIndex("SessionId") - .HasDatabaseName("ix_action_logs_session_id"); - - b.ToTable("action_logs", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Badge", 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("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.Sphere.Account.CheckInResult", 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("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.Sphere.Account.MagicSpell", 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.Sphere.Account.Notification", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("Content") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .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>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("Priority") - .HasColumnType("integer") - .HasColumnName("priority"); - - b.Property("Subtitle") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("subtitle"); - - b.Property("Title") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("title"); - - b.Property("Topic") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("topic"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("ViewedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("viewed_at"); - - b.HasKey("Id") - .HasName("pk_notifications"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_notifications_account_id"); - - b.ToTable("notifications", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.NotificationPushSubscription", 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(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("device_id"); - - b.Property("DeviceToken") - .IsRequired() - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("device_token"); - - b.Property("LastUsedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_used_at"); - - b.Property("Provider") - .HasColumnType("integer") - .HasColumnName("provider"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_notification_push_subscriptions"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_notification_push_subscriptions_account_id"); - - b.HasIndex("DeviceId") - .IsUnique() - .HasDatabaseName("ix_notification_push_subscriptions_device_id"); - - b.HasIndex("DeviceToken") - .IsUnique() - .HasDatabaseName("ix_notification_push_subscriptions_device_token"); - - b.ToTable("notification_push_subscriptions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Profile", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_id"); - - 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("MiddleName") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("middle_name"); - - b.Property("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - b.Property("Pronouns") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("pronouns"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_account_profiles"); - - b.HasIndex("AccountId") - .IsUnique() - .HasDatabaseName("ix_account_profiles_account_id"); - - b.HasIndex("BackgroundId") - .HasDatabaseName("ix_account_profiles_background_id"); - - b.HasIndex("PictureId") - .HasDatabaseName("ix_account_profiles_picture_id"); - - b.ToTable("account_profiles", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Relationship", 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("integer") - .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.Sphere.Account.Status", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - 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("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("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.Sphere.Activity.Activity", 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>("Meta") - .IsRequired() - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("ResourceIdentifier") - .IsRequired() - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("resource_identifier"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property>("UsersVisible") - .IsRequired() - .HasColumnType("jsonb") - .HasColumnName("users_visible"); - - b.Property("Visibility") - .HasColumnType("integer") - .HasColumnName("visibility"); - - b.HasKey("Id") - .HasName("pk_activities"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_activities_account_id"); - - b.ToTable("activities", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Challenge", 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") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("device_id"); - - 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("geometry") - .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("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_challenges"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_auth_challenges_account_id"); - - b.ToTable("auth_challenges", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Session", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("ChallengeId") - .HasColumnType("uuid") - .HasColumnName("challenge_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("Label") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("label"); - - b.Property("LastGrantedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_granted_at"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - 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.ToTable("auth_sessions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatMember", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("ChatRoomId") - .HasColumnType("uuid") - .HasColumnName("chat_room_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("IsBot") - .HasColumnType("boolean") - .HasColumnName("is_bot"); - - b.Property("JoinedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("joined_at"); - - b.Property("LastReadAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_read_at"); - - b.Property("LeaveAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("leave_at"); - - b.Property("Nick") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("nick"); - - b.Property("Notify") - .HasColumnType("integer") - .HasColumnName("notify"); - - b.Property("Role") - .HasColumnType("integer") - .HasColumnName("role"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_chat_members"); - - b.HasAlternateKey("ChatRoomId", "AccountId") - .HasName("ak_chat_members_chat_room_id_account_id"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_chat_members_account_id"); - - b.ToTable("chat_members", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatRoom", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_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("Description") - .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") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - b.Property("RealmId") - .HasColumnType("uuid") - .HasColumnName("realm_id"); - - b.Property("Type") - .HasColumnType("integer") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_chat_rooms"); - - b.HasIndex("BackgroundId") - .HasDatabaseName("ix_chat_rooms_background_id"); - - b.HasIndex("PictureId") - .HasDatabaseName("ix_chat_rooms_picture_id"); - - b.HasIndex("RealmId") - .HasDatabaseName("ix_chat_rooms_realm_id"); - - b.ToTable("chat_rooms", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.Message", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("ChatRoomId") - .HasColumnType("uuid") - .HasColumnName("chat_room_id"); - - b.Property("Content") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .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("EditedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("edited_at"); - - b.Property("ForwardedMessageId") - .HasColumnType("uuid") - .HasColumnName("forwarded_message_id"); - - b.Property>("MembersMentioned") - .HasColumnType("jsonb") - .HasColumnName("members_mentioned"); - - b.Property>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("Nonce") - .IsRequired() - .HasMaxLength(36) - .HasColumnType("character varying(36)") - .HasColumnName("nonce"); - - b.Property("RepliedMessageId") - .HasColumnType("uuid") - .HasColumnName("replied_message_id"); - - b.Property("SenderId") - .HasColumnType("uuid") - .HasColumnName("sender_id"); - - 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_chat_messages"); - - b.HasIndex("ChatRoomId") - .HasDatabaseName("ix_chat_messages_chat_room_id"); - - b.HasIndex("ForwardedMessageId") - .HasDatabaseName("ix_chat_messages_forwarded_message_id"); - - b.HasIndex("RepliedMessageId") - .HasDatabaseName("ix_chat_messages_replied_message_id"); - - b.HasIndex("SenderId") - .HasDatabaseName("ix_chat_messages_sender_id"); - - b.ToTable("chat_messages", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.MessageReaction", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("Attitude") - .HasColumnType("integer") - .HasColumnName("attitude"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("MessageId") - .HasColumnType("uuid") - .HasColumnName("message_id"); - - b.Property("SenderId") - .HasColumnType("uuid") - .HasColumnName("sender_id"); - - b.Property("Symbol") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("symbol"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_chat_reactions"); - - b.HasIndex("MessageId") - .HasDatabaseName("ix_chat_reactions_message_id"); - - b.HasIndex("SenderId") - .HasDatabaseName("ix_chat_reactions_sender_id"); - - b.ToTable("chat_reactions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.RealtimeCall", 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("EndedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("ended_at"); - - b.Property("RoomId") - .HasColumnType("uuid") - .HasColumnName("room_id"); - - b.Property("SenderId") - .HasColumnType("uuid") - .HasColumnName("sender_id"); - - b.Property("Title") - .HasColumnType("text") - .HasColumnName("title"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_chat_realtime_call"); - - b.HasIndex("RoomId") - .HasDatabaseName("ix_chat_realtime_call_room_id"); - - b.HasIndex("SenderId") - .HasDatabaseName("ix_chat_realtime_call_sender_id"); - - b.ToTable("chat_realtime_call", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomApp", 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("Name") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("slug"); - - b.Property("Status") - .HasColumnType("integer") - .HasColumnName("status"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("VerifiedAs") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("verified_as"); - - b.Property("VerifiedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("verified_at"); - - b.HasKey("Id") - .HasName("pk_custom_apps"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_custom_apps_publisher_id"); - - b.ToTable("custom_apps", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomAppSecret", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AppId") - .HasColumnType("uuid") - .HasColumnName("app_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("Remarks") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("remarks"); - - b.Property("Secret") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("secret"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_custom_app_secrets"); - - b.HasIndex("AppId") - .HasDatabaseName("ix_custom_app_secrets_app_id"); - - b.ToTable("custom_app_secrets", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroup", 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.Sphere.Permission.PermissionGroupMember", 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.Sphere.Permission.PermissionNode", 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("Area") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("area"); - - 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("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", "Area", "Actor") - .HasDatabaseName("ix_permission_nodes_key_area_actor"); - - b.ToTable("permission_nodes", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.Post", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("Content") - .HasColumnType("text") - .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("Description") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("Downvotes") - .HasColumnType("integer") - .HasColumnName("downvotes"); - - b.Property("EditedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("edited_at"); - - b.Property("ForwardedPostId") - .HasColumnType("uuid") - .HasColumnName("forwarded_post_id"); - - b.Property("Language") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("language"); - - b.Property>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("PublishedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("published_at"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("RepliedPostId") - .HasColumnType("uuid") - .HasColumnName("replied_post_id"); - - b.Property("SearchVector") - .IsRequired() - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("tsvector") - .HasColumnName("search_vector") - .HasAnnotation("Npgsql:TsVectorConfig", "simple") - .HasAnnotation("Npgsql:TsVectorProperties", new[] { "Title", "Description", "Content" }); - - b.Property("ThreadedPostId") - .HasColumnType("uuid") - .HasColumnName("threaded_post_id"); - - b.Property("Title") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("title"); - - b.Property("Type") - .HasColumnType("integer") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("Upvotes") - .HasColumnType("integer") - .HasColumnName("upvotes"); - - b.Property("ViewsTotal") - .HasColumnType("integer") - .HasColumnName("views_total"); - - b.Property("ViewsUnique") - .HasColumnType("integer") - .HasColumnName("views_unique"); - - b.Property("Visibility") - .HasColumnType("integer") - .HasColumnName("visibility"); - - b.HasKey("Id") - .HasName("pk_posts"); - - b.HasIndex("ForwardedPostId") - .HasDatabaseName("ix_posts_forwarded_post_id"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_posts_publisher_id"); - - b.HasIndex("RepliedPostId") - .HasDatabaseName("ix_posts_replied_post_id"); - - b.HasIndex("SearchVector") - .HasDatabaseName("ix_posts_search_vector"); - - NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex("SearchVector"), "GIN"); - - b.HasIndex("ThreadedPostId") - .IsUnique() - .HasDatabaseName("ix_posts_threaded_post_id"); - - b.ToTable("posts", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostCategory", 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("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("name"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_categories"); - - b.ToTable("post_categories", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostCollection", 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("Description") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("name"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_collections"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_post_collections_publisher_id"); - - b.ToTable("post_collections", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostReaction", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("Attitude") - .HasColumnType("integer") - .HasColumnName("attitude"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("PostId") - .HasColumnType("uuid") - .HasColumnName("post_id"); - - b.Property("Symbol") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("symbol"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_reactions"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_post_reactions_account_id"); - - b.HasIndex("PostId") - .HasDatabaseName("ix_post_reactions_post_id"); - - b.ToTable("post_reactions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostTag", 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("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("name"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_tags"); - - b.ToTable("post_tags", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.Publisher", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("BackgroundId") - .HasColumnType("character varying(32)") - .HasColumnName("background_id"); - - b.Property("Bio") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("bio"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - 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("PictureId") - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - b.Property("RealmId") - .HasColumnType("uuid") - .HasColumnName("realm_id"); - - b.Property("Type") - .HasColumnType("integer") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_publishers"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_publishers_account_id"); - - b.HasIndex("BackgroundId") - .HasDatabaseName("ix_publishers_background_id"); - - b.HasIndex("Name") - .IsUnique() - .HasDatabaseName("ix_publishers_name"); - - b.HasIndex("PictureId") - .HasDatabaseName("ix_publishers_picture_id"); - - b.HasIndex("RealmId") - .HasDatabaseName("ix_publishers_realm_id"); - - b.ToTable("publishers", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherFeature", 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("ExpiredAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expired_at"); - - b.Property("Flag") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("flag"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_publisher_features"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_publisher_features_publisher_id"); - - b.ToTable("publisher_features", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherMember", b => - { - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_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("Role") - .HasColumnType("integer") - .HasColumnName("role"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("PublisherId", "AccountId") - .HasName("pk_publisher_members"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_publisher_members_account_id"); - - b.ToTable("publisher_members", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherSubscription", 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("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("Status") - .HasColumnType("integer") - .HasColumnName("status"); - - b.Property("Tier") - .HasColumnType("integer") - .HasColumnName("tier"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_publisher_subscriptions"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_publisher_subscriptions_account_id"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_publisher_subscriptions_publisher_id"); - - b.ToTable("publisher_subscriptions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.Realm", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_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("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("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - 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("VerifiedAs") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("verified_as"); - - b.Property("VerifiedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("verified_at"); - - b.HasKey("Id") - .HasName("pk_realms"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_realms_account_id"); - - b.HasIndex("BackgroundId") - .HasDatabaseName("ix_realms_background_id"); - - b.HasIndex("PictureId") - .HasDatabaseName("ix_realms_picture_id"); - - b.HasIndex("Slug") - .IsUnique() - .HasDatabaseName("ix_realms_slug"); - - b.ToTable("realms", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.RealmMember", 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.HasIndex("AccountId") - .HasDatabaseName("ix_realm_members_account_id"); - - b.ToTable("realm_members", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.Sticker", 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("ImageId") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("image_id"); - - b.Property("PackId") - .HasColumnType("uuid") - .HasColumnName("pack_id"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_stickers"); - - b.HasIndex("ImageId") - .HasDatabaseName("ix_stickers_image_id"); - - b.HasIndex("PackId") - .HasDatabaseName("ix_stickers_pack_id"); - - b.HasIndex("Slug") - .HasDatabaseName("ix_stickers_slug"); - - b.ToTable("stickers", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.StickerPack", 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("Description") - .IsRequired() - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("Prefix") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("prefix"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_sticker_packs"); - - b.HasIndex("Prefix") - .IsUnique() - .HasDatabaseName("ix_sticker_packs_prefix"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_sticker_packs_publisher_id"); - - b.ToTable("sticker_packs", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFile", b => - { - b.Property("Id") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .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("Description") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("ExpiredAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expired_at"); - - b.Property>("FileMeta") - .HasColumnType("jsonb") - .HasColumnName("file_meta"); - - b.Property("HasCompression") - .HasColumnType("boolean") - .HasColumnName("has_compression"); - - b.Property("Hash") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("hash"); - - b.Property("MessageId") - .HasColumnType("uuid") - .HasColumnName("message_id"); - - b.Property("MimeType") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("mime_type"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("PostId") - .HasColumnType("uuid") - .HasColumnName("post_id"); - - b.Property>("SensitiveMarks") - .HasColumnType("jsonb") - .HasColumnName("sensitive_marks"); - - b.Property("Size") - .HasColumnType("bigint") - .HasColumnName("size"); - - b.Property("StorageId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("storage_id"); - - b.Property("StorageUrl") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("storage_url"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("UploadedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("uploaded_at"); - - b.Property("UploadedTo") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("uploaded_to"); - - b.Property("UsedCount") - .HasColumnType("integer") - .HasColumnName("used_count"); - - b.Property>("UserMeta") - .HasColumnType("jsonb") - .HasColumnName("user_meta"); - - b.HasKey("Id") - .HasName("pk_files"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_files_account_id"); - - b.HasIndex("MessageId") - .HasDatabaseName("ix_files_message_id"); - - b.HasIndex("PostId") - .HasDatabaseName("ix_files_post_id"); - - b.ToTable("files", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Order", 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("ExpiredAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expired_at"); - - b.Property("IssuerAppId") - .HasColumnType("uuid") - .HasColumnName("issuer_app_id"); - - b.Property("PayeeWalletId") - .HasColumnType("uuid") - .HasColumnName("payee_wallet_id"); - - 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("IssuerAppId") - .HasDatabaseName("ix_payment_orders_issuer_app_id"); - - 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.Sphere.Wallet.Transaction", 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.Sphere.Wallet.Wallet", 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.Sphere.Wallet.WalletPocket", 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("PostPostCategory", b => - { - b.Property("CategoriesId") - .HasColumnType("uuid") - .HasColumnName("categories_id"); - - b.Property("PostsId") - .HasColumnType("uuid") - .HasColumnName("posts_id"); - - b.HasKey("CategoriesId", "PostsId") - .HasName("pk_post_category_links"); - - b.HasIndex("PostsId") - .HasDatabaseName("ix_post_category_links_posts_id"); - - b.ToTable("post_category_links", (string)null); - }); - - modelBuilder.Entity("PostPostCollection", b => - { - b.Property("CollectionsId") - .HasColumnType("uuid") - .HasColumnName("collections_id"); - - b.Property("PostsId") - .HasColumnType("uuid") - .HasColumnName("posts_id"); - - b.HasKey("CollectionsId", "PostsId") - .HasName("pk_post_collection_links"); - - b.HasIndex("PostsId") - .HasDatabaseName("ix_post_collection_links_posts_id"); - - b.ToTable("post_collection_links", (string)null); - }); - - modelBuilder.Entity("PostPostTag", b => - { - b.Property("PostsId") - .HasColumnType("uuid") - .HasColumnName("posts_id"); - - b.Property("TagsId") - .HasColumnType("uuid") - .HasColumnName("tags_id"); - - b.HasKey("PostsId", "TagsId") - .HasName("pk_post_tag_links"); - - b.HasIndex("TagsId") - .HasDatabaseName("ix_post_tag_links_tags_id"); - - b.ToTable("post_tag_links", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.AccountAuthFactor", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("AuthFactors") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_auth_factors_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.AccountContact", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Contacts") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_contacts_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.ActionLog", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_action_logs_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Auth.Session", "Session") - .WithMany() - .HasForeignKey("SessionId") - .HasConstraintName("fk_action_logs_auth_sessions_session_id"); - - b.Navigation("Account"); - - b.Navigation("Session"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Badge", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Badges") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_badges_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.CheckInResult", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_check_in_results_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.MagicSpell", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .HasConstraintName("fk_magic_spells_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Notification", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_notifications_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.NotificationPushSubscription", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_notification_push_subscriptions_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Profile", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithOne("Profile") - .HasForeignKey("DysonNetwork.Sphere.Account.Profile", "AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_profiles_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "Background") - .WithMany() - .HasForeignKey("BackgroundId") - .HasConstraintName("fk_account_profiles_files_background_id"); - - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "Picture") - .WithMany() - .HasForeignKey("PictureId") - .HasConstraintName("fk_account_profiles_files_picture_id"); - - b.Navigation("Account"); - - b.Navigation("Background"); - - b.Navigation("Picture"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Relationship", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("OutgoingRelationships") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_relationships_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Account.Account", "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.Sphere.Account.Status", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_statuses_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Activity.Activity", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_activities_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Challenge", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Challenges") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_challenges_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Session", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Sessions") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_sessions_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Auth.Challenge", "Challenge") - .WithMany() - .HasForeignKey("ChallengeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_sessions_auth_challenges_challenge_id"); - - b.Navigation("Account"); - - b.Navigation("Challenge"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatMember", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_members_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatRoom", "ChatRoom") - .WithMany("Members") - .HasForeignKey("ChatRoomId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_members_chat_rooms_chat_room_id"); - - b.Navigation("Account"); - - b.Navigation("ChatRoom"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatRoom", b => - { - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "Background") - .WithMany() - .HasForeignKey("BackgroundId") - .HasConstraintName("fk_chat_rooms_files_background_id"); - - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "Picture") - .WithMany() - .HasForeignKey("PictureId") - .HasConstraintName("fk_chat_rooms_files_picture_id"); - - b.HasOne("DysonNetwork.Sphere.Realm.Realm", "Realm") - .WithMany("ChatRooms") - .HasForeignKey("RealmId") - .HasConstraintName("fk_chat_rooms_realms_realm_id"); - - b.Navigation("Background"); - - b.Navigation("Picture"); - - b.Navigation("Realm"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.Message", b => - { - b.HasOne("DysonNetwork.Sphere.Chat.ChatRoom", "ChatRoom") - .WithMany() - .HasForeignKey("ChatRoomId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_messages_chat_rooms_chat_room_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.Message", "ForwardedMessage") - .WithMany() - .HasForeignKey("ForwardedMessageId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_chat_messages_chat_messages_forwarded_message_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.Message", "RepliedMessage") - .WithMany() - .HasForeignKey("RepliedMessageId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_chat_messages_chat_messages_replied_message_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatMember", "Sender") - .WithMany() - .HasForeignKey("SenderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_messages_chat_members_sender_id"); - - b.Navigation("ChatRoom"); - - b.Navigation("ForwardedMessage"); - - b.Navigation("RepliedMessage"); - - b.Navigation("Sender"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.MessageReaction", b => - { - b.HasOne("DysonNetwork.Sphere.Chat.Message", "Message") - .WithMany("Reactions") - .HasForeignKey("MessageId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_reactions_chat_messages_message_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatMember", "Sender") - .WithMany() - .HasForeignKey("SenderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_reactions_chat_members_sender_id"); - - b.Navigation("Message"); - - b.Navigation("Sender"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.RealtimeCall", b => - { - b.HasOne("DysonNetwork.Sphere.Chat.ChatRoom", "Room") - .WithMany() - .HasForeignKey("RoomId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_realtime_call_chat_rooms_room_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatMember", "Sender") - .WithMany() - .HasForeignKey("SenderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_realtime_call_chat_members_sender_id"); - - b.Navigation("Room"); - - b.Navigation("Sender"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomApp", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Developer") - .WithMany() - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_custom_apps_publishers_publisher_id"); - - b.Navigation("Developer"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomAppSecret", b => - { - b.HasOne("DysonNetwork.Sphere.Developer.CustomApp", "App") - .WithMany() - .HasForeignKey("AppId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_custom_app_secrets_custom_apps_app_id"); - - b.Navigation("App"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroupMember", b => - { - b.HasOne("DysonNetwork.Sphere.Permission.PermissionGroup", "Group") - .WithMany("Members") - .HasForeignKey("GroupId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_permission_group_members_permission_groups_group_id"); - - b.Navigation("Group"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionNode", b => - { - b.HasOne("DysonNetwork.Sphere.Permission.PermissionGroup", "Group") - .WithMany("Nodes") - .HasForeignKey("GroupId") - .HasConstraintName("fk_permission_nodes_permission_groups_group_id"); - - b.Navigation("Group"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.Post", b => - { - b.HasOne("DysonNetwork.Sphere.Post.Post", "ForwardedPost") - .WithMany() - .HasForeignKey("ForwardedPostId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_posts_posts_forwarded_post_id"); - - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Posts") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_posts_publishers_publisher_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", "RepliedPost") - .WithMany() - .HasForeignKey("RepliedPostId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_posts_posts_replied_post_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", "ThreadedPost") - .WithOne() - .HasForeignKey("DysonNetwork.Sphere.Post.Post", "ThreadedPostId") - .HasConstraintName("fk_posts_posts_threaded_post_id"); - - b.Navigation("ForwardedPost"); - - b.Navigation("Publisher"); - - b.Navigation("RepliedPost"); - - b.Navigation("ThreadedPost"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostCollection", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Collections") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_collections_publishers_publisher_id"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostReaction", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_reactions_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", "Post") - .WithMany("Reactions") - .HasForeignKey("PostId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_reactions_posts_post_id"); - - b.Navigation("Account"); - - b.Navigation("Post"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.Publisher", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .HasConstraintName("fk_publishers_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "Background") - .WithMany() - .HasForeignKey("BackgroundId") - .HasConstraintName("fk_publishers_files_background_id"); - - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "Picture") - .WithMany() - .HasForeignKey("PictureId") - .HasConstraintName("fk_publishers_files_picture_id"); - - b.HasOne("DysonNetwork.Sphere.Realm.Realm", "Realm") - .WithMany() - .HasForeignKey("RealmId") - .HasConstraintName("fk_publishers_realms_realm_id"); - - b.Navigation("Account"); - - b.Navigation("Background"); - - b.Navigation("Picture"); - - b.Navigation("Realm"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherFeature", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany() - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_features_publishers_publisher_id"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherMember", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_members_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Members") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_members_publishers_publisher_id"); - - b.Navigation("Account"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherSubscription", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_subscriptions_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Subscriptions") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_subscriptions_publishers_publisher_id"); - - b.Navigation("Account"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.Realm", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_realms_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "Background") - .WithMany() - .HasForeignKey("BackgroundId") - .HasConstraintName("fk_realms_files_background_id"); - - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "Picture") - .WithMany() - .HasForeignKey("PictureId") - .HasConstraintName("fk_realms_files_picture_id"); - - b.Navigation("Account"); - - b.Navigation("Background"); - - b.Navigation("Picture"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.RealmMember", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_realm_members_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Realm.Realm", "Realm") - .WithMany("Members") - .HasForeignKey("RealmId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_realm_members_realms_realm_id"); - - b.Navigation("Account"); - - b.Navigation("Realm"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.Sticker", b => - { - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "Image") - .WithMany() - .HasForeignKey("ImageId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_stickers_files_image_id"); - - b.HasOne("DysonNetwork.Sphere.Sticker.StickerPack", "Pack") - .WithMany() - .HasForeignKey("PackId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_stickers_sticker_packs_pack_id"); - - b.Navigation("Image"); - - b.Navigation("Pack"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.StickerPack", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany() - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_sticker_packs_publishers_publisher_id"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFile", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_files_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.Message", null) - .WithMany("Attachments") - .HasForeignKey("MessageId") - .HasConstraintName("fk_files_chat_messages_message_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany("Attachments") - .HasForeignKey("PostId") - .HasConstraintName("fk_files_posts_post_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Order", b => - { - b.HasOne("DysonNetwork.Sphere.Developer.CustomApp", "IssuerApp") - .WithMany() - .HasForeignKey("IssuerAppId") - .HasConstraintName("fk_payment_orders_custom_apps_issuer_app_id"); - - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "PayeeWallet") - .WithMany() - .HasForeignKey("PayeeWalletId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_payment_orders_wallets_payee_wallet_id"); - - b.HasOne("DysonNetwork.Sphere.Wallet.Transaction", "Transaction") - .WithMany() - .HasForeignKey("TransactionId") - .HasConstraintName("fk_payment_orders_payment_transactions_transaction_id"); - - b.Navigation("IssuerApp"); - - b.Navigation("PayeeWallet"); - - b.Navigation("Transaction"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Transaction", b => - { - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "PayeeWallet") - .WithMany() - .HasForeignKey("PayeeWalletId") - .HasConstraintName("fk_payment_transactions_wallets_payee_wallet_id"); - - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "PayerWallet") - .WithMany() - .HasForeignKey("PayerWalletId") - .HasConstraintName("fk_payment_transactions_wallets_payer_wallet_id"); - - b.Navigation("PayeeWallet"); - - b.Navigation("PayerWallet"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Wallet", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_wallets_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.WalletPocket", b => - { - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "Wallet") - .WithMany("Pockets") - .HasForeignKey("WalletId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_wallet_pockets_wallets_wallet_id"); - - b.Navigation("Wallet"); - }); - - modelBuilder.Entity("PostPostCategory", b => - { - b.HasOne("DysonNetwork.Sphere.Post.PostCategory", null) - .WithMany() - .HasForeignKey("CategoriesId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_category_links_post_categories_categories_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany() - .HasForeignKey("PostsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_category_links_posts_posts_id"); - }); - - modelBuilder.Entity("PostPostCollection", b => - { - b.HasOne("DysonNetwork.Sphere.Post.PostCollection", null) - .WithMany() - .HasForeignKey("CollectionsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_collection_links_post_collections_collections_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany() - .HasForeignKey("PostsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_collection_links_posts_posts_id"); - }); - - modelBuilder.Entity("PostPostTag", b => - { - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany() - .HasForeignKey("PostsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_tag_links_posts_posts_id"); - - b.HasOne("DysonNetwork.Sphere.Post.PostTag", null) - .WithMany() - .HasForeignKey("TagsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_tag_links_post_tags_tags_id"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Account", b => - { - b.Navigation("AuthFactors"); - - b.Navigation("Badges"); - - b.Navigation("Challenges"); - - b.Navigation("Contacts"); - - b.Navigation("IncomingRelationships"); - - b.Navigation("OutgoingRelationships"); - - b.Navigation("Profile") - .IsRequired(); - - b.Navigation("Sessions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatRoom", b => - { - b.Navigation("Members"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.Message", b => - { - b.Navigation("Attachments"); - - b.Navigation("Reactions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroup", b => - { - b.Navigation("Members"); - - b.Navigation("Nodes"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.Post", b => - { - b.Navigation("Attachments"); - - b.Navigation("Reactions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.Publisher", b => - { - b.Navigation("Collections"); - - b.Navigation("Members"); - - b.Navigation("Posts"); - - b.Navigation("Subscriptions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.Realm", b => - { - b.Navigation("ChatRooms"); - - b.Navigation("Members"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Wallet", b => - { - b.Navigation("Pockets"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/DysonNetwork.Sphere/Migrations/20250523172951_RefactorChatLastRead.cs b/DysonNetwork.Sphere/Migrations/20250523172951_RefactorChatLastRead.cs deleted file mode 100644 index c3e5fda..0000000 --- a/DysonNetwork.Sphere/Migrations/20250523172951_RefactorChatLastRead.cs +++ /dev/null @@ -1,89 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; -using NodaTime; - -#nullable disable - -namespace DysonNetwork.Sphere.Migrations -{ - /// - public partial class RefactorChatLastRead : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "chat_read_receipts"); - - migrationBuilder.AlterColumn( - name: "type", - table: "chat_messages", - type: "character varying(1024)", - maxLength: 1024, - nullable: false, - oldClrType: typeof(string), - oldType: "text"); - - migrationBuilder.AddColumn( - name: "last_read_at", - table: "chat_members", - type: "timestamp with time zone", - nullable: true); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "last_read_at", - table: "chat_members"); - - migrationBuilder.AlterColumn( - name: "type", - table: "chat_messages", - type: "text", - nullable: false, - oldClrType: typeof(string), - oldType: "character varying(1024)", - oldMaxLength: 1024); - - migrationBuilder.CreateTable( - name: "chat_read_receipts", - columns: table => new - { - message_id = table.Column(type: "uuid", nullable: false), - sender_id = table.Column(type: "uuid", nullable: false), - created_at = table.Column(type: "timestamp with time zone", nullable: false), - deleted_at = table.Column(type: "timestamp with time zone", nullable: true), - updated_at = table.Column(type: "timestamp with time zone", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("pk_chat_read_receipts", x => new { x.message_id, x.sender_id }); - table.ForeignKey( - name: "fk_chat_read_receipts_chat_members_sender_id", - column: x => x.sender_id, - principalTable: "chat_members", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "fk_chat_read_receipts_chat_messages_message_id", - column: x => x.message_id, - principalTable: "chat_messages", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateIndex( - name: "ix_chat_read_receipts_message_id_sender_id", - table: "chat_read_receipts", - columns: new[] { "message_id", "sender_id" }, - unique: true); - - migrationBuilder.CreateIndex( - name: "ix_chat_read_receipts_sender_id", - table: "chat_read_receipts", - column: "sender_id"); - } - } -} diff --git a/DysonNetwork.Sphere/Migrations/20250524215045_UpdateRealtimeChat.Designer.cs b/DysonNetwork.Sphere/Migrations/20250524215045_UpdateRealtimeChat.Designer.cs deleted file mode 100644 index c758b4f..0000000 --- a/DysonNetwork.Sphere/Migrations/20250524215045_UpdateRealtimeChat.Designer.cs +++ /dev/null @@ -1,3400 +0,0 @@ -// -using System; -using System.Collections.Generic; -using System.Text.Json; -using DysonNetwork.Sphere; -using DysonNetwork.Sphere.Account; -using DysonNetwork.Sphere.Storage; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using NetTopologySuite.Geometries; -using NodaTime; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; -using NpgsqlTypes; - -#nullable disable - -namespace DysonNetwork.Sphere.Migrations -{ - [DbContext(typeof(AppDatabase))] - [Migration("20250524215045_UpdateRealtimeChat")] - partial class UpdateRealtimeChat - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "9.0.3") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "postgis"); - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Account", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("ActivatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("activated_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("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("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.Sphere.Account.AccountAuthFactor", 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("Secret") - .HasMaxLength(8196) - .HasColumnType("character varying(8196)") - .HasColumnName("secret"); - - 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.Sphere.Account.AccountContact", 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("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.Sphere.Account.ActionLog", 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("geometry") - .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.HasIndex("SessionId") - .HasDatabaseName("ix_action_logs_session_id"); - - b.ToTable("action_logs", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Badge", 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("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.Sphere.Account.CheckInResult", 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("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.Sphere.Account.MagicSpell", 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.Sphere.Account.Notification", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("Content") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .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>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("Priority") - .HasColumnType("integer") - .HasColumnName("priority"); - - b.Property("Subtitle") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("subtitle"); - - b.Property("Title") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("title"); - - b.Property("Topic") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("topic"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("ViewedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("viewed_at"); - - b.HasKey("Id") - .HasName("pk_notifications"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_notifications_account_id"); - - b.ToTable("notifications", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.NotificationPushSubscription", 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(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("device_id"); - - b.Property("DeviceToken") - .IsRequired() - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("device_token"); - - b.Property("LastUsedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_used_at"); - - b.Property("Provider") - .HasColumnType("integer") - .HasColumnName("provider"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_notification_push_subscriptions"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_notification_push_subscriptions_account_id"); - - b.HasIndex("DeviceToken", "DeviceId") - .IsUnique() - .HasDatabaseName("ix_notification_push_subscriptions_device_token_device_id"); - - b.ToTable("notification_push_subscriptions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Profile", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_id"); - - 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("MiddleName") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("middle_name"); - - b.Property("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - b.Property("Pronouns") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("pronouns"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_account_profiles"); - - b.HasIndex("AccountId") - .IsUnique() - .HasDatabaseName("ix_account_profiles_account_id"); - - b.HasIndex("BackgroundId") - .HasDatabaseName("ix_account_profiles_background_id"); - - b.HasIndex("PictureId") - .HasDatabaseName("ix_account_profiles_picture_id"); - - b.ToTable("account_profiles", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Relationship", 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("integer") - .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.Sphere.Account.Status", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - 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("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("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.Sphere.Activity.Activity", 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>("Meta") - .IsRequired() - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("ResourceIdentifier") - .IsRequired() - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("resource_identifier"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property>("UsersVisible") - .IsRequired() - .HasColumnType("jsonb") - .HasColumnName("users_visible"); - - b.Property("Visibility") - .HasColumnType("integer") - .HasColumnName("visibility"); - - b.HasKey("Id") - .HasName("pk_activities"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_activities_account_id"); - - b.ToTable("activities", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Challenge", 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") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("device_id"); - - 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("geometry") - .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("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_challenges"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_auth_challenges_account_id"); - - b.ToTable("auth_challenges", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Session", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("ChallengeId") - .HasColumnType("uuid") - .HasColumnName("challenge_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("Label") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("label"); - - b.Property("LastGrantedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_granted_at"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - 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.ToTable("auth_sessions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatMember", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("ChatRoomId") - .HasColumnType("uuid") - .HasColumnName("chat_room_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("IsBot") - .HasColumnType("boolean") - .HasColumnName("is_bot"); - - b.Property("JoinedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("joined_at"); - - b.Property("LastReadAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_read_at"); - - b.Property("LeaveAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("leave_at"); - - b.Property("Nick") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("nick"); - - b.Property("Notify") - .HasColumnType("integer") - .HasColumnName("notify"); - - b.Property("Role") - .HasColumnType("integer") - .HasColumnName("role"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_chat_members"); - - b.HasAlternateKey("ChatRoomId", "AccountId") - .HasName("ak_chat_members_chat_room_id_account_id"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_chat_members_account_id"); - - b.ToTable("chat_members", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatRoom", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_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("Description") - .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") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - b.Property("RealmId") - .HasColumnType("uuid") - .HasColumnName("realm_id"); - - b.Property("Type") - .HasColumnType("integer") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_chat_rooms"); - - b.HasIndex("BackgroundId") - .HasDatabaseName("ix_chat_rooms_background_id"); - - b.HasIndex("PictureId") - .HasDatabaseName("ix_chat_rooms_picture_id"); - - b.HasIndex("RealmId") - .HasDatabaseName("ix_chat_rooms_realm_id"); - - b.ToTable("chat_rooms", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.Message", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("ChatRoomId") - .HasColumnType("uuid") - .HasColumnName("chat_room_id"); - - b.Property("Content") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .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("EditedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("edited_at"); - - b.Property("ForwardedMessageId") - .HasColumnType("uuid") - .HasColumnName("forwarded_message_id"); - - b.Property>("MembersMentioned") - .HasColumnType("jsonb") - .HasColumnName("members_mentioned"); - - b.Property>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("Nonce") - .IsRequired() - .HasMaxLength(36) - .HasColumnType("character varying(36)") - .HasColumnName("nonce"); - - b.Property("RepliedMessageId") - .HasColumnType("uuid") - .HasColumnName("replied_message_id"); - - b.Property("SenderId") - .HasColumnType("uuid") - .HasColumnName("sender_id"); - - 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_chat_messages"); - - b.HasIndex("ChatRoomId") - .HasDatabaseName("ix_chat_messages_chat_room_id"); - - b.HasIndex("ForwardedMessageId") - .HasDatabaseName("ix_chat_messages_forwarded_message_id"); - - b.HasIndex("RepliedMessageId") - .HasDatabaseName("ix_chat_messages_replied_message_id"); - - b.HasIndex("SenderId") - .HasDatabaseName("ix_chat_messages_sender_id"); - - b.ToTable("chat_messages", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.MessageReaction", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("Attitude") - .HasColumnType("integer") - .HasColumnName("attitude"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("MessageId") - .HasColumnType("uuid") - .HasColumnName("message_id"); - - b.Property("SenderId") - .HasColumnType("uuid") - .HasColumnName("sender_id"); - - b.Property("Symbol") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("symbol"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_chat_reactions"); - - b.HasIndex("MessageId") - .HasDatabaseName("ix_chat_reactions_message_id"); - - b.HasIndex("SenderId") - .HasDatabaseName("ix_chat_reactions_sender_id"); - - b.ToTable("chat_reactions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.RealtimeCall", 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("EndedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("ended_at"); - - b.Property("ProviderName") - .HasColumnType("text") - .HasColumnName("provider_name"); - - b.Property("RoomId") - .HasColumnType("uuid") - .HasColumnName("room_id"); - - b.Property("SenderId") - .HasColumnType("uuid") - .HasColumnName("sender_id"); - - b.Property("SessionId") - .HasColumnType("text") - .HasColumnName("session_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("UpstreamConfigJson") - .HasColumnType("jsonb") - .HasColumnName("upstream"); - - b.HasKey("Id") - .HasName("pk_chat_realtime_call"); - - b.HasIndex("RoomId") - .HasDatabaseName("ix_chat_realtime_call_room_id"); - - b.HasIndex("SenderId") - .HasDatabaseName("ix_chat_realtime_call_sender_id"); - - b.ToTable("chat_realtime_call", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomApp", 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("Name") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("slug"); - - b.Property("Status") - .HasColumnType("integer") - .HasColumnName("status"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("VerifiedAs") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("verified_as"); - - b.Property("VerifiedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("verified_at"); - - b.HasKey("Id") - .HasName("pk_custom_apps"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_custom_apps_publisher_id"); - - b.ToTable("custom_apps", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomAppSecret", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AppId") - .HasColumnType("uuid") - .HasColumnName("app_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("Remarks") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("remarks"); - - b.Property("Secret") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("secret"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_custom_app_secrets"); - - b.HasIndex("AppId") - .HasDatabaseName("ix_custom_app_secrets_app_id"); - - b.ToTable("custom_app_secrets", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroup", 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.Sphere.Permission.PermissionGroupMember", 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.Sphere.Permission.PermissionNode", 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("Area") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("area"); - - 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("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", "Area", "Actor") - .HasDatabaseName("ix_permission_nodes_key_area_actor"); - - b.ToTable("permission_nodes", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.Post", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("Content") - .HasColumnType("text") - .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("Description") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("Downvotes") - .HasColumnType("integer") - .HasColumnName("downvotes"); - - b.Property("EditedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("edited_at"); - - b.Property("ForwardedPostId") - .HasColumnType("uuid") - .HasColumnName("forwarded_post_id"); - - b.Property("Language") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("language"); - - b.Property>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("PublishedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("published_at"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("RepliedPostId") - .HasColumnType("uuid") - .HasColumnName("replied_post_id"); - - b.Property("SearchVector") - .IsRequired() - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("tsvector") - .HasColumnName("search_vector") - .HasAnnotation("Npgsql:TsVectorConfig", "simple") - .HasAnnotation("Npgsql:TsVectorProperties", new[] { "Title", "Description", "Content" }); - - b.Property("ThreadedPostId") - .HasColumnType("uuid") - .HasColumnName("threaded_post_id"); - - b.Property("Title") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("title"); - - b.Property("Type") - .HasColumnType("integer") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("Upvotes") - .HasColumnType("integer") - .HasColumnName("upvotes"); - - b.Property("ViewsTotal") - .HasColumnType("integer") - .HasColumnName("views_total"); - - b.Property("ViewsUnique") - .HasColumnType("integer") - .HasColumnName("views_unique"); - - b.Property("Visibility") - .HasColumnType("integer") - .HasColumnName("visibility"); - - b.HasKey("Id") - .HasName("pk_posts"); - - b.HasIndex("ForwardedPostId") - .HasDatabaseName("ix_posts_forwarded_post_id"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_posts_publisher_id"); - - b.HasIndex("RepliedPostId") - .HasDatabaseName("ix_posts_replied_post_id"); - - b.HasIndex("SearchVector") - .HasDatabaseName("ix_posts_search_vector"); - - NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex("SearchVector"), "GIN"); - - b.HasIndex("ThreadedPostId") - .IsUnique() - .HasDatabaseName("ix_posts_threaded_post_id"); - - b.ToTable("posts", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostCategory", 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("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("name"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_categories"); - - b.ToTable("post_categories", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostCollection", 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("Description") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("name"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_collections"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_post_collections_publisher_id"); - - b.ToTable("post_collections", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostReaction", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("Attitude") - .HasColumnType("integer") - .HasColumnName("attitude"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("PostId") - .HasColumnType("uuid") - .HasColumnName("post_id"); - - b.Property("Symbol") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("symbol"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_reactions"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_post_reactions_account_id"); - - b.HasIndex("PostId") - .HasDatabaseName("ix_post_reactions_post_id"); - - b.ToTable("post_reactions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostTag", 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("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("name"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_tags"); - - b.ToTable("post_tags", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.Publisher", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("BackgroundId") - .HasColumnType("character varying(32)") - .HasColumnName("background_id"); - - b.Property("Bio") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("bio"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - 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("PictureId") - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - b.Property("RealmId") - .HasColumnType("uuid") - .HasColumnName("realm_id"); - - b.Property("Type") - .HasColumnType("integer") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_publishers"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_publishers_account_id"); - - b.HasIndex("BackgroundId") - .HasDatabaseName("ix_publishers_background_id"); - - b.HasIndex("Name") - .IsUnique() - .HasDatabaseName("ix_publishers_name"); - - b.HasIndex("PictureId") - .HasDatabaseName("ix_publishers_picture_id"); - - b.HasIndex("RealmId") - .HasDatabaseName("ix_publishers_realm_id"); - - b.ToTable("publishers", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherFeature", 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("ExpiredAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expired_at"); - - b.Property("Flag") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("flag"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_publisher_features"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_publisher_features_publisher_id"); - - b.ToTable("publisher_features", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherMember", b => - { - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_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("Role") - .HasColumnType("integer") - .HasColumnName("role"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("PublisherId", "AccountId") - .HasName("pk_publisher_members"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_publisher_members_account_id"); - - b.ToTable("publisher_members", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherSubscription", 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("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("Status") - .HasColumnType("integer") - .HasColumnName("status"); - - b.Property("Tier") - .HasColumnType("integer") - .HasColumnName("tier"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_publisher_subscriptions"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_publisher_subscriptions_account_id"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_publisher_subscriptions_publisher_id"); - - b.ToTable("publisher_subscriptions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.Realm", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_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("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("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - 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("VerifiedAs") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("verified_as"); - - b.Property("VerifiedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("verified_at"); - - b.HasKey("Id") - .HasName("pk_realms"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_realms_account_id"); - - b.HasIndex("BackgroundId") - .HasDatabaseName("ix_realms_background_id"); - - b.HasIndex("PictureId") - .HasDatabaseName("ix_realms_picture_id"); - - b.HasIndex("Slug") - .IsUnique() - .HasDatabaseName("ix_realms_slug"); - - b.ToTable("realms", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.RealmMember", 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.HasIndex("AccountId") - .HasDatabaseName("ix_realm_members_account_id"); - - b.ToTable("realm_members", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.Sticker", 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("ImageId") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("image_id"); - - b.Property("PackId") - .HasColumnType("uuid") - .HasColumnName("pack_id"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_stickers"); - - b.HasIndex("ImageId") - .HasDatabaseName("ix_stickers_image_id"); - - b.HasIndex("PackId") - .HasDatabaseName("ix_stickers_pack_id"); - - b.HasIndex("Slug") - .HasDatabaseName("ix_stickers_slug"); - - b.ToTable("stickers", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.StickerPack", 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("Description") - .IsRequired() - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("Prefix") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("prefix"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_sticker_packs"); - - b.HasIndex("Prefix") - .IsUnique() - .HasDatabaseName("ix_sticker_packs_prefix"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_sticker_packs_publisher_id"); - - b.ToTable("sticker_packs", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFile", b => - { - b.Property("Id") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .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("Description") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("ExpiredAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expired_at"); - - b.Property>("FileMeta") - .HasColumnType("jsonb") - .HasColumnName("file_meta"); - - b.Property("HasCompression") - .HasColumnType("boolean") - .HasColumnName("has_compression"); - - b.Property("Hash") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("hash"); - - b.Property("MessageId") - .HasColumnType("uuid") - .HasColumnName("message_id"); - - b.Property("MimeType") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("mime_type"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("PostId") - .HasColumnType("uuid") - .HasColumnName("post_id"); - - b.Property>("SensitiveMarks") - .HasColumnType("jsonb") - .HasColumnName("sensitive_marks"); - - b.Property("Size") - .HasColumnType("bigint") - .HasColumnName("size"); - - b.Property("StorageId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("storage_id"); - - b.Property("StorageUrl") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("storage_url"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("UploadedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("uploaded_at"); - - b.Property("UploadedTo") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("uploaded_to"); - - b.Property("UsedCount") - .HasColumnType("integer") - .HasColumnName("used_count"); - - b.Property>("UserMeta") - .HasColumnType("jsonb") - .HasColumnName("user_meta"); - - b.HasKey("Id") - .HasName("pk_files"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_files_account_id"); - - b.HasIndex("MessageId") - .HasDatabaseName("ix_files_message_id"); - - b.HasIndex("PostId") - .HasDatabaseName("ix_files_post_id"); - - b.ToTable("files", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Order", 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("ExpiredAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expired_at"); - - b.Property("IssuerAppId") - .HasColumnType("uuid") - .HasColumnName("issuer_app_id"); - - b.Property("PayeeWalletId") - .HasColumnType("uuid") - .HasColumnName("payee_wallet_id"); - - 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("IssuerAppId") - .HasDatabaseName("ix_payment_orders_issuer_app_id"); - - 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.Sphere.Wallet.Transaction", 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.Sphere.Wallet.Wallet", 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.Sphere.Wallet.WalletPocket", 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("PostPostCategory", b => - { - b.Property("CategoriesId") - .HasColumnType("uuid") - .HasColumnName("categories_id"); - - b.Property("PostsId") - .HasColumnType("uuid") - .HasColumnName("posts_id"); - - b.HasKey("CategoriesId", "PostsId") - .HasName("pk_post_category_links"); - - b.HasIndex("PostsId") - .HasDatabaseName("ix_post_category_links_posts_id"); - - b.ToTable("post_category_links", (string)null); - }); - - modelBuilder.Entity("PostPostCollection", b => - { - b.Property("CollectionsId") - .HasColumnType("uuid") - .HasColumnName("collections_id"); - - b.Property("PostsId") - .HasColumnType("uuid") - .HasColumnName("posts_id"); - - b.HasKey("CollectionsId", "PostsId") - .HasName("pk_post_collection_links"); - - b.HasIndex("PostsId") - .HasDatabaseName("ix_post_collection_links_posts_id"); - - b.ToTable("post_collection_links", (string)null); - }); - - modelBuilder.Entity("PostPostTag", b => - { - b.Property("PostsId") - .HasColumnType("uuid") - .HasColumnName("posts_id"); - - b.Property("TagsId") - .HasColumnType("uuid") - .HasColumnName("tags_id"); - - b.HasKey("PostsId", "TagsId") - .HasName("pk_post_tag_links"); - - b.HasIndex("TagsId") - .HasDatabaseName("ix_post_tag_links_tags_id"); - - b.ToTable("post_tag_links", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.AccountAuthFactor", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("AuthFactors") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_auth_factors_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.AccountContact", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Contacts") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_contacts_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.ActionLog", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_action_logs_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Auth.Session", "Session") - .WithMany() - .HasForeignKey("SessionId") - .HasConstraintName("fk_action_logs_auth_sessions_session_id"); - - b.Navigation("Account"); - - b.Navigation("Session"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Badge", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Badges") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_badges_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.CheckInResult", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_check_in_results_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.MagicSpell", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .HasConstraintName("fk_magic_spells_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Notification", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_notifications_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.NotificationPushSubscription", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_notification_push_subscriptions_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Profile", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithOne("Profile") - .HasForeignKey("DysonNetwork.Sphere.Account.Profile", "AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_profiles_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "Background") - .WithMany() - .HasForeignKey("BackgroundId") - .HasConstraintName("fk_account_profiles_files_background_id"); - - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "Picture") - .WithMany() - .HasForeignKey("PictureId") - .HasConstraintName("fk_account_profiles_files_picture_id"); - - b.Navigation("Account"); - - b.Navigation("Background"); - - b.Navigation("Picture"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Relationship", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("OutgoingRelationships") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_relationships_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Account.Account", "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.Sphere.Account.Status", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_statuses_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Activity.Activity", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_activities_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Challenge", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Challenges") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_challenges_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Session", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Sessions") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_sessions_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Auth.Challenge", "Challenge") - .WithMany() - .HasForeignKey("ChallengeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_sessions_auth_challenges_challenge_id"); - - b.Navigation("Account"); - - b.Navigation("Challenge"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatMember", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_members_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatRoom", "ChatRoom") - .WithMany("Members") - .HasForeignKey("ChatRoomId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_members_chat_rooms_chat_room_id"); - - b.Navigation("Account"); - - b.Navigation("ChatRoom"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatRoom", b => - { - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "Background") - .WithMany() - .HasForeignKey("BackgroundId") - .HasConstraintName("fk_chat_rooms_files_background_id"); - - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "Picture") - .WithMany() - .HasForeignKey("PictureId") - .HasConstraintName("fk_chat_rooms_files_picture_id"); - - b.HasOne("DysonNetwork.Sphere.Realm.Realm", "Realm") - .WithMany("ChatRooms") - .HasForeignKey("RealmId") - .HasConstraintName("fk_chat_rooms_realms_realm_id"); - - b.Navigation("Background"); - - b.Navigation("Picture"); - - b.Navigation("Realm"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.Message", b => - { - b.HasOne("DysonNetwork.Sphere.Chat.ChatRoom", "ChatRoom") - .WithMany() - .HasForeignKey("ChatRoomId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_messages_chat_rooms_chat_room_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.Message", "ForwardedMessage") - .WithMany() - .HasForeignKey("ForwardedMessageId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_chat_messages_chat_messages_forwarded_message_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.Message", "RepliedMessage") - .WithMany() - .HasForeignKey("RepliedMessageId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_chat_messages_chat_messages_replied_message_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatMember", "Sender") - .WithMany() - .HasForeignKey("SenderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_messages_chat_members_sender_id"); - - b.Navigation("ChatRoom"); - - b.Navigation("ForwardedMessage"); - - b.Navigation("RepliedMessage"); - - b.Navigation("Sender"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.MessageReaction", b => - { - b.HasOne("DysonNetwork.Sphere.Chat.Message", "Message") - .WithMany("Reactions") - .HasForeignKey("MessageId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_reactions_chat_messages_message_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatMember", "Sender") - .WithMany() - .HasForeignKey("SenderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_reactions_chat_members_sender_id"); - - b.Navigation("Message"); - - b.Navigation("Sender"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.RealtimeCall", b => - { - b.HasOne("DysonNetwork.Sphere.Chat.ChatRoom", "Room") - .WithMany() - .HasForeignKey("RoomId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_realtime_call_chat_rooms_room_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatMember", "Sender") - .WithMany() - .HasForeignKey("SenderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_realtime_call_chat_members_sender_id"); - - b.Navigation("Room"); - - b.Navigation("Sender"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomApp", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Developer") - .WithMany() - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_custom_apps_publishers_publisher_id"); - - b.Navigation("Developer"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomAppSecret", b => - { - b.HasOne("DysonNetwork.Sphere.Developer.CustomApp", "App") - .WithMany() - .HasForeignKey("AppId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_custom_app_secrets_custom_apps_app_id"); - - b.Navigation("App"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroupMember", b => - { - b.HasOne("DysonNetwork.Sphere.Permission.PermissionGroup", "Group") - .WithMany("Members") - .HasForeignKey("GroupId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_permission_group_members_permission_groups_group_id"); - - b.Navigation("Group"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionNode", b => - { - b.HasOne("DysonNetwork.Sphere.Permission.PermissionGroup", "Group") - .WithMany("Nodes") - .HasForeignKey("GroupId") - .HasConstraintName("fk_permission_nodes_permission_groups_group_id"); - - b.Navigation("Group"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.Post", b => - { - b.HasOne("DysonNetwork.Sphere.Post.Post", "ForwardedPost") - .WithMany() - .HasForeignKey("ForwardedPostId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_posts_posts_forwarded_post_id"); - - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Posts") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_posts_publishers_publisher_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", "RepliedPost") - .WithMany() - .HasForeignKey("RepliedPostId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_posts_posts_replied_post_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", "ThreadedPost") - .WithOne() - .HasForeignKey("DysonNetwork.Sphere.Post.Post", "ThreadedPostId") - .HasConstraintName("fk_posts_posts_threaded_post_id"); - - b.Navigation("ForwardedPost"); - - b.Navigation("Publisher"); - - b.Navigation("RepliedPost"); - - b.Navigation("ThreadedPost"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostCollection", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Collections") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_collections_publishers_publisher_id"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostReaction", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_reactions_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", "Post") - .WithMany("Reactions") - .HasForeignKey("PostId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_reactions_posts_post_id"); - - b.Navigation("Account"); - - b.Navigation("Post"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.Publisher", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .HasConstraintName("fk_publishers_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "Background") - .WithMany() - .HasForeignKey("BackgroundId") - .HasConstraintName("fk_publishers_files_background_id"); - - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "Picture") - .WithMany() - .HasForeignKey("PictureId") - .HasConstraintName("fk_publishers_files_picture_id"); - - b.HasOne("DysonNetwork.Sphere.Realm.Realm", "Realm") - .WithMany() - .HasForeignKey("RealmId") - .HasConstraintName("fk_publishers_realms_realm_id"); - - b.Navigation("Account"); - - b.Navigation("Background"); - - b.Navigation("Picture"); - - b.Navigation("Realm"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherFeature", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany() - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_features_publishers_publisher_id"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherMember", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_members_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Members") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_members_publishers_publisher_id"); - - b.Navigation("Account"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherSubscription", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_subscriptions_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Subscriptions") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_subscriptions_publishers_publisher_id"); - - b.Navigation("Account"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.Realm", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_realms_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "Background") - .WithMany() - .HasForeignKey("BackgroundId") - .HasConstraintName("fk_realms_files_background_id"); - - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "Picture") - .WithMany() - .HasForeignKey("PictureId") - .HasConstraintName("fk_realms_files_picture_id"); - - b.Navigation("Account"); - - b.Navigation("Background"); - - b.Navigation("Picture"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.RealmMember", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_realm_members_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Realm.Realm", "Realm") - .WithMany("Members") - .HasForeignKey("RealmId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_realm_members_realms_realm_id"); - - b.Navigation("Account"); - - b.Navigation("Realm"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.Sticker", b => - { - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "Image") - .WithMany() - .HasForeignKey("ImageId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_stickers_files_image_id"); - - b.HasOne("DysonNetwork.Sphere.Sticker.StickerPack", "Pack") - .WithMany() - .HasForeignKey("PackId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_stickers_sticker_packs_pack_id"); - - b.Navigation("Image"); - - b.Navigation("Pack"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.StickerPack", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany() - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_sticker_packs_publishers_publisher_id"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFile", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_files_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.Message", null) - .WithMany("Attachments") - .HasForeignKey("MessageId") - .HasConstraintName("fk_files_chat_messages_message_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany("Attachments") - .HasForeignKey("PostId") - .HasConstraintName("fk_files_posts_post_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Order", b => - { - b.HasOne("DysonNetwork.Sphere.Developer.CustomApp", "IssuerApp") - .WithMany() - .HasForeignKey("IssuerAppId") - .HasConstraintName("fk_payment_orders_custom_apps_issuer_app_id"); - - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "PayeeWallet") - .WithMany() - .HasForeignKey("PayeeWalletId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_payment_orders_wallets_payee_wallet_id"); - - b.HasOne("DysonNetwork.Sphere.Wallet.Transaction", "Transaction") - .WithMany() - .HasForeignKey("TransactionId") - .HasConstraintName("fk_payment_orders_payment_transactions_transaction_id"); - - b.Navigation("IssuerApp"); - - b.Navigation("PayeeWallet"); - - b.Navigation("Transaction"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Transaction", b => - { - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "PayeeWallet") - .WithMany() - .HasForeignKey("PayeeWalletId") - .HasConstraintName("fk_payment_transactions_wallets_payee_wallet_id"); - - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "PayerWallet") - .WithMany() - .HasForeignKey("PayerWalletId") - .HasConstraintName("fk_payment_transactions_wallets_payer_wallet_id"); - - b.Navigation("PayeeWallet"); - - b.Navigation("PayerWallet"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Wallet", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_wallets_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.WalletPocket", b => - { - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "Wallet") - .WithMany("Pockets") - .HasForeignKey("WalletId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_wallet_pockets_wallets_wallet_id"); - - b.Navigation("Wallet"); - }); - - modelBuilder.Entity("PostPostCategory", b => - { - b.HasOne("DysonNetwork.Sphere.Post.PostCategory", null) - .WithMany() - .HasForeignKey("CategoriesId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_category_links_post_categories_categories_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany() - .HasForeignKey("PostsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_category_links_posts_posts_id"); - }); - - modelBuilder.Entity("PostPostCollection", b => - { - b.HasOne("DysonNetwork.Sphere.Post.PostCollection", null) - .WithMany() - .HasForeignKey("CollectionsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_collection_links_post_collections_collections_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany() - .HasForeignKey("PostsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_collection_links_posts_posts_id"); - }); - - modelBuilder.Entity("PostPostTag", b => - { - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany() - .HasForeignKey("PostsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_tag_links_posts_posts_id"); - - b.HasOne("DysonNetwork.Sphere.Post.PostTag", null) - .WithMany() - .HasForeignKey("TagsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_tag_links_post_tags_tags_id"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Account", b => - { - b.Navigation("AuthFactors"); - - b.Navigation("Badges"); - - b.Navigation("Challenges"); - - b.Navigation("Contacts"); - - b.Navigation("IncomingRelationships"); - - b.Navigation("OutgoingRelationships"); - - b.Navigation("Profile") - .IsRequired(); - - b.Navigation("Sessions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatRoom", b => - { - b.Navigation("Members"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.Message", b => - { - b.Navigation("Attachments"); - - b.Navigation("Reactions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroup", b => - { - b.Navigation("Members"); - - b.Navigation("Nodes"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.Post", b => - { - b.Navigation("Attachments"); - - b.Navigation("Reactions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.Publisher", b => - { - b.Navigation("Collections"); - - b.Navigation("Members"); - - b.Navigation("Posts"); - - b.Navigation("Subscriptions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.Realm", b => - { - b.Navigation("ChatRooms"); - - b.Navigation("Members"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Wallet", b => - { - b.Navigation("Pockets"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/DysonNetwork.Sphere/Migrations/20250524215045_UpdateRealtimeChat.cs b/DysonNetwork.Sphere/Migrations/20250524215045_UpdateRealtimeChat.cs deleted file mode 100644 index 27d5a3e..0000000 --- a/DysonNetwork.Sphere/Migrations/20250524215045_UpdateRealtimeChat.cs +++ /dev/null @@ -1,78 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace DysonNetwork.Sphere.Migrations -{ - /// - public partial class UpdateRealtimeChat : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropIndex( - name: "ix_notification_push_subscriptions_device_id", - table: "notification_push_subscriptions"); - - migrationBuilder.DropIndex( - name: "ix_notification_push_subscriptions_device_token", - table: "notification_push_subscriptions"); - - migrationBuilder.RenameColumn( - name: "title", - table: "chat_realtime_call", - newName: "session_id"); - - migrationBuilder.AddColumn( - name: "provider_name", - table: "chat_realtime_call", - type: "text", - nullable: true); - - migrationBuilder.AddColumn( - name: "upstream", - table: "chat_realtime_call", - type: "jsonb", - nullable: true); - - migrationBuilder.CreateIndex( - name: "ix_notification_push_subscriptions_device_token_device_id", - table: "notification_push_subscriptions", - columns: new[] { "device_token", "device_id" }, - unique: true); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropIndex( - name: "ix_notification_push_subscriptions_device_token_device_id", - table: "notification_push_subscriptions"); - - migrationBuilder.DropColumn( - name: "provider_name", - table: "chat_realtime_call"); - - migrationBuilder.DropColumn( - name: "upstream", - table: "chat_realtime_call"); - - migrationBuilder.RenameColumn( - name: "session_id", - table: "chat_realtime_call", - newName: "title"); - - migrationBuilder.CreateIndex( - name: "ix_notification_push_subscriptions_device_id", - table: "notification_push_subscriptions", - column: "device_id", - unique: true); - - migrationBuilder.CreateIndex( - name: "ix_notification_push_subscriptions_device_token", - table: "notification_push_subscriptions", - column: "device_token", - unique: true); - } - } -} diff --git a/DysonNetwork.Sphere/Migrations/20250525083412_ModifyRelationshipStatusType.Designer.cs b/DysonNetwork.Sphere/Migrations/20250525083412_ModifyRelationshipStatusType.Designer.cs deleted file mode 100644 index 9192fe8..0000000 --- a/DysonNetwork.Sphere/Migrations/20250525083412_ModifyRelationshipStatusType.Designer.cs +++ /dev/null @@ -1,3400 +0,0 @@ -// -using System; -using System.Collections.Generic; -using System.Text.Json; -using DysonNetwork.Sphere; -using DysonNetwork.Sphere.Account; -using DysonNetwork.Sphere.Storage; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using NetTopologySuite.Geometries; -using NodaTime; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; -using NpgsqlTypes; - -#nullable disable - -namespace DysonNetwork.Sphere.Migrations -{ - [DbContext(typeof(AppDatabase))] - [Migration("20250525083412_ModifyRelationshipStatusType")] - partial class ModifyRelationshipStatusType - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "9.0.3") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "postgis"); - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Account", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("ActivatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("activated_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("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("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.Sphere.Account.AccountAuthFactor", 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("Secret") - .HasMaxLength(8196) - .HasColumnType("character varying(8196)") - .HasColumnName("secret"); - - 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.Sphere.Account.AccountContact", 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("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.Sphere.Account.ActionLog", 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("geometry") - .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.HasIndex("SessionId") - .HasDatabaseName("ix_action_logs_session_id"); - - b.ToTable("action_logs", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Badge", 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("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.Sphere.Account.CheckInResult", 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("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.Sphere.Account.MagicSpell", 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.Sphere.Account.Notification", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("Content") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .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>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("Priority") - .HasColumnType("integer") - .HasColumnName("priority"); - - b.Property("Subtitle") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("subtitle"); - - b.Property("Title") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("title"); - - b.Property("Topic") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("topic"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("ViewedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("viewed_at"); - - b.HasKey("Id") - .HasName("pk_notifications"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_notifications_account_id"); - - b.ToTable("notifications", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.NotificationPushSubscription", 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(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("device_id"); - - b.Property("DeviceToken") - .IsRequired() - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("device_token"); - - b.Property("LastUsedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_used_at"); - - b.Property("Provider") - .HasColumnType("integer") - .HasColumnName("provider"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_notification_push_subscriptions"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_notification_push_subscriptions_account_id"); - - b.HasIndex("DeviceToken", "DeviceId") - .IsUnique() - .HasDatabaseName("ix_notification_push_subscriptions_device_token_device_id"); - - b.ToTable("notification_push_subscriptions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Profile", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_id"); - - 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("MiddleName") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("middle_name"); - - b.Property("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - b.Property("Pronouns") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("pronouns"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_account_profiles"); - - b.HasIndex("AccountId") - .IsUnique() - .HasDatabaseName("ix_account_profiles_account_id"); - - b.HasIndex("BackgroundId") - .HasDatabaseName("ix_account_profiles_background_id"); - - b.HasIndex("PictureId") - .HasDatabaseName("ix_account_profiles_picture_id"); - - b.ToTable("account_profiles", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Relationship", 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.Sphere.Account.Status", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - 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("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("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.Sphere.Activity.Activity", 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>("Meta") - .IsRequired() - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("ResourceIdentifier") - .IsRequired() - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("resource_identifier"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property>("UsersVisible") - .IsRequired() - .HasColumnType("jsonb") - .HasColumnName("users_visible"); - - b.Property("Visibility") - .HasColumnType("integer") - .HasColumnName("visibility"); - - b.HasKey("Id") - .HasName("pk_activities"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_activities_account_id"); - - b.ToTable("activities", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Challenge", 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") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("device_id"); - - 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("geometry") - .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("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_challenges"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_auth_challenges_account_id"); - - b.ToTable("auth_challenges", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Session", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("ChallengeId") - .HasColumnType("uuid") - .HasColumnName("challenge_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("Label") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("label"); - - b.Property("LastGrantedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_granted_at"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - 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.ToTable("auth_sessions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatMember", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("ChatRoomId") - .HasColumnType("uuid") - .HasColumnName("chat_room_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("IsBot") - .HasColumnType("boolean") - .HasColumnName("is_bot"); - - b.Property("JoinedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("joined_at"); - - b.Property("LastReadAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_read_at"); - - b.Property("LeaveAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("leave_at"); - - b.Property("Nick") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("nick"); - - b.Property("Notify") - .HasColumnType("integer") - .HasColumnName("notify"); - - b.Property("Role") - .HasColumnType("integer") - .HasColumnName("role"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_chat_members"); - - b.HasAlternateKey("ChatRoomId", "AccountId") - .HasName("ak_chat_members_chat_room_id_account_id"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_chat_members_account_id"); - - b.ToTable("chat_members", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatRoom", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_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("Description") - .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") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - b.Property("RealmId") - .HasColumnType("uuid") - .HasColumnName("realm_id"); - - b.Property("Type") - .HasColumnType("integer") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_chat_rooms"); - - b.HasIndex("BackgroundId") - .HasDatabaseName("ix_chat_rooms_background_id"); - - b.HasIndex("PictureId") - .HasDatabaseName("ix_chat_rooms_picture_id"); - - b.HasIndex("RealmId") - .HasDatabaseName("ix_chat_rooms_realm_id"); - - b.ToTable("chat_rooms", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.Message", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("ChatRoomId") - .HasColumnType("uuid") - .HasColumnName("chat_room_id"); - - b.Property("Content") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .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("EditedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("edited_at"); - - b.Property("ForwardedMessageId") - .HasColumnType("uuid") - .HasColumnName("forwarded_message_id"); - - b.Property>("MembersMentioned") - .HasColumnType("jsonb") - .HasColumnName("members_mentioned"); - - b.Property>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("Nonce") - .IsRequired() - .HasMaxLength(36) - .HasColumnType("character varying(36)") - .HasColumnName("nonce"); - - b.Property("RepliedMessageId") - .HasColumnType("uuid") - .HasColumnName("replied_message_id"); - - b.Property("SenderId") - .HasColumnType("uuid") - .HasColumnName("sender_id"); - - 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_chat_messages"); - - b.HasIndex("ChatRoomId") - .HasDatabaseName("ix_chat_messages_chat_room_id"); - - b.HasIndex("ForwardedMessageId") - .HasDatabaseName("ix_chat_messages_forwarded_message_id"); - - b.HasIndex("RepliedMessageId") - .HasDatabaseName("ix_chat_messages_replied_message_id"); - - b.HasIndex("SenderId") - .HasDatabaseName("ix_chat_messages_sender_id"); - - b.ToTable("chat_messages", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.MessageReaction", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("Attitude") - .HasColumnType("integer") - .HasColumnName("attitude"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("MessageId") - .HasColumnType("uuid") - .HasColumnName("message_id"); - - b.Property("SenderId") - .HasColumnType("uuid") - .HasColumnName("sender_id"); - - b.Property("Symbol") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("symbol"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_chat_reactions"); - - b.HasIndex("MessageId") - .HasDatabaseName("ix_chat_reactions_message_id"); - - b.HasIndex("SenderId") - .HasDatabaseName("ix_chat_reactions_sender_id"); - - b.ToTable("chat_reactions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.RealtimeCall", 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("EndedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("ended_at"); - - b.Property("ProviderName") - .HasColumnType("text") - .HasColumnName("provider_name"); - - b.Property("RoomId") - .HasColumnType("uuid") - .HasColumnName("room_id"); - - b.Property("SenderId") - .HasColumnType("uuid") - .HasColumnName("sender_id"); - - b.Property("SessionId") - .HasColumnType("text") - .HasColumnName("session_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("UpstreamConfigJson") - .HasColumnType("jsonb") - .HasColumnName("upstream"); - - b.HasKey("Id") - .HasName("pk_chat_realtime_call"); - - b.HasIndex("RoomId") - .HasDatabaseName("ix_chat_realtime_call_room_id"); - - b.HasIndex("SenderId") - .HasDatabaseName("ix_chat_realtime_call_sender_id"); - - b.ToTable("chat_realtime_call", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomApp", 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("Name") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("slug"); - - b.Property("Status") - .HasColumnType("integer") - .HasColumnName("status"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("VerifiedAs") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("verified_as"); - - b.Property("VerifiedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("verified_at"); - - b.HasKey("Id") - .HasName("pk_custom_apps"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_custom_apps_publisher_id"); - - b.ToTable("custom_apps", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomAppSecret", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AppId") - .HasColumnType("uuid") - .HasColumnName("app_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("Remarks") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("remarks"); - - b.Property("Secret") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("secret"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_custom_app_secrets"); - - b.HasIndex("AppId") - .HasDatabaseName("ix_custom_app_secrets_app_id"); - - b.ToTable("custom_app_secrets", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroup", 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.Sphere.Permission.PermissionGroupMember", 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.Sphere.Permission.PermissionNode", 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("Area") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("area"); - - 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("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", "Area", "Actor") - .HasDatabaseName("ix_permission_nodes_key_area_actor"); - - b.ToTable("permission_nodes", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.Post", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("Content") - .HasColumnType("text") - .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("Description") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("Downvotes") - .HasColumnType("integer") - .HasColumnName("downvotes"); - - b.Property("EditedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("edited_at"); - - b.Property("ForwardedPostId") - .HasColumnType("uuid") - .HasColumnName("forwarded_post_id"); - - b.Property("Language") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("language"); - - b.Property>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("PublishedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("published_at"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("RepliedPostId") - .HasColumnType("uuid") - .HasColumnName("replied_post_id"); - - b.Property("SearchVector") - .IsRequired() - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("tsvector") - .HasColumnName("search_vector") - .HasAnnotation("Npgsql:TsVectorConfig", "simple") - .HasAnnotation("Npgsql:TsVectorProperties", new[] { "Title", "Description", "Content" }); - - b.Property("ThreadedPostId") - .HasColumnType("uuid") - .HasColumnName("threaded_post_id"); - - b.Property("Title") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("title"); - - b.Property("Type") - .HasColumnType("integer") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("Upvotes") - .HasColumnType("integer") - .HasColumnName("upvotes"); - - b.Property("ViewsTotal") - .HasColumnType("integer") - .HasColumnName("views_total"); - - b.Property("ViewsUnique") - .HasColumnType("integer") - .HasColumnName("views_unique"); - - b.Property("Visibility") - .HasColumnType("integer") - .HasColumnName("visibility"); - - b.HasKey("Id") - .HasName("pk_posts"); - - b.HasIndex("ForwardedPostId") - .HasDatabaseName("ix_posts_forwarded_post_id"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_posts_publisher_id"); - - b.HasIndex("RepliedPostId") - .HasDatabaseName("ix_posts_replied_post_id"); - - b.HasIndex("SearchVector") - .HasDatabaseName("ix_posts_search_vector"); - - NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex("SearchVector"), "GIN"); - - b.HasIndex("ThreadedPostId") - .IsUnique() - .HasDatabaseName("ix_posts_threaded_post_id"); - - b.ToTable("posts", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostCategory", 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("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("name"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_categories"); - - b.ToTable("post_categories", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostCollection", 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("Description") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("name"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_collections"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_post_collections_publisher_id"); - - b.ToTable("post_collections", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostReaction", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("Attitude") - .HasColumnType("integer") - .HasColumnName("attitude"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("PostId") - .HasColumnType("uuid") - .HasColumnName("post_id"); - - b.Property("Symbol") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("symbol"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_reactions"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_post_reactions_account_id"); - - b.HasIndex("PostId") - .HasDatabaseName("ix_post_reactions_post_id"); - - b.ToTable("post_reactions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostTag", 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("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("name"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_tags"); - - b.ToTable("post_tags", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.Publisher", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("BackgroundId") - .HasColumnType("character varying(32)") - .HasColumnName("background_id"); - - b.Property("Bio") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("bio"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - 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("PictureId") - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - b.Property("RealmId") - .HasColumnType("uuid") - .HasColumnName("realm_id"); - - b.Property("Type") - .HasColumnType("integer") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_publishers"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_publishers_account_id"); - - b.HasIndex("BackgroundId") - .HasDatabaseName("ix_publishers_background_id"); - - b.HasIndex("Name") - .IsUnique() - .HasDatabaseName("ix_publishers_name"); - - b.HasIndex("PictureId") - .HasDatabaseName("ix_publishers_picture_id"); - - b.HasIndex("RealmId") - .HasDatabaseName("ix_publishers_realm_id"); - - b.ToTable("publishers", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherFeature", 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("ExpiredAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expired_at"); - - b.Property("Flag") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("flag"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_publisher_features"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_publisher_features_publisher_id"); - - b.ToTable("publisher_features", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherMember", b => - { - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_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("Role") - .HasColumnType("integer") - .HasColumnName("role"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("PublisherId", "AccountId") - .HasName("pk_publisher_members"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_publisher_members_account_id"); - - b.ToTable("publisher_members", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherSubscription", 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("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("Status") - .HasColumnType("integer") - .HasColumnName("status"); - - b.Property("Tier") - .HasColumnType("integer") - .HasColumnName("tier"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_publisher_subscriptions"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_publisher_subscriptions_account_id"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_publisher_subscriptions_publisher_id"); - - b.ToTable("publisher_subscriptions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.Realm", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_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("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("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - 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("VerifiedAs") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("verified_as"); - - b.Property("VerifiedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("verified_at"); - - b.HasKey("Id") - .HasName("pk_realms"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_realms_account_id"); - - b.HasIndex("BackgroundId") - .HasDatabaseName("ix_realms_background_id"); - - b.HasIndex("PictureId") - .HasDatabaseName("ix_realms_picture_id"); - - b.HasIndex("Slug") - .IsUnique() - .HasDatabaseName("ix_realms_slug"); - - b.ToTable("realms", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.RealmMember", 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.HasIndex("AccountId") - .HasDatabaseName("ix_realm_members_account_id"); - - b.ToTable("realm_members", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.Sticker", 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("ImageId") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("image_id"); - - b.Property("PackId") - .HasColumnType("uuid") - .HasColumnName("pack_id"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_stickers"); - - b.HasIndex("ImageId") - .HasDatabaseName("ix_stickers_image_id"); - - b.HasIndex("PackId") - .HasDatabaseName("ix_stickers_pack_id"); - - b.HasIndex("Slug") - .HasDatabaseName("ix_stickers_slug"); - - b.ToTable("stickers", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.StickerPack", 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("Description") - .IsRequired() - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("Prefix") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("prefix"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_sticker_packs"); - - b.HasIndex("Prefix") - .IsUnique() - .HasDatabaseName("ix_sticker_packs_prefix"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_sticker_packs_publisher_id"); - - b.ToTable("sticker_packs", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFile", b => - { - b.Property("Id") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .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("Description") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("ExpiredAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expired_at"); - - b.Property>("FileMeta") - .HasColumnType("jsonb") - .HasColumnName("file_meta"); - - b.Property("HasCompression") - .HasColumnType("boolean") - .HasColumnName("has_compression"); - - b.Property("Hash") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("hash"); - - b.Property("MessageId") - .HasColumnType("uuid") - .HasColumnName("message_id"); - - b.Property("MimeType") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("mime_type"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("PostId") - .HasColumnType("uuid") - .HasColumnName("post_id"); - - b.Property>("SensitiveMarks") - .HasColumnType("jsonb") - .HasColumnName("sensitive_marks"); - - b.Property("Size") - .HasColumnType("bigint") - .HasColumnName("size"); - - b.Property("StorageId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("storage_id"); - - b.Property("StorageUrl") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("storage_url"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("UploadedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("uploaded_at"); - - b.Property("UploadedTo") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("uploaded_to"); - - b.Property("UsedCount") - .HasColumnType("integer") - .HasColumnName("used_count"); - - b.Property>("UserMeta") - .HasColumnType("jsonb") - .HasColumnName("user_meta"); - - b.HasKey("Id") - .HasName("pk_files"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_files_account_id"); - - b.HasIndex("MessageId") - .HasDatabaseName("ix_files_message_id"); - - b.HasIndex("PostId") - .HasDatabaseName("ix_files_post_id"); - - b.ToTable("files", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Order", 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("ExpiredAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expired_at"); - - b.Property("IssuerAppId") - .HasColumnType("uuid") - .HasColumnName("issuer_app_id"); - - b.Property("PayeeWalletId") - .HasColumnType("uuid") - .HasColumnName("payee_wallet_id"); - - 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("IssuerAppId") - .HasDatabaseName("ix_payment_orders_issuer_app_id"); - - 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.Sphere.Wallet.Transaction", 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.Sphere.Wallet.Wallet", 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.Sphere.Wallet.WalletPocket", 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("PostPostCategory", b => - { - b.Property("CategoriesId") - .HasColumnType("uuid") - .HasColumnName("categories_id"); - - b.Property("PostsId") - .HasColumnType("uuid") - .HasColumnName("posts_id"); - - b.HasKey("CategoriesId", "PostsId") - .HasName("pk_post_category_links"); - - b.HasIndex("PostsId") - .HasDatabaseName("ix_post_category_links_posts_id"); - - b.ToTable("post_category_links", (string)null); - }); - - modelBuilder.Entity("PostPostCollection", b => - { - b.Property("CollectionsId") - .HasColumnType("uuid") - .HasColumnName("collections_id"); - - b.Property("PostsId") - .HasColumnType("uuid") - .HasColumnName("posts_id"); - - b.HasKey("CollectionsId", "PostsId") - .HasName("pk_post_collection_links"); - - b.HasIndex("PostsId") - .HasDatabaseName("ix_post_collection_links_posts_id"); - - b.ToTable("post_collection_links", (string)null); - }); - - modelBuilder.Entity("PostPostTag", b => - { - b.Property("PostsId") - .HasColumnType("uuid") - .HasColumnName("posts_id"); - - b.Property("TagsId") - .HasColumnType("uuid") - .HasColumnName("tags_id"); - - b.HasKey("PostsId", "TagsId") - .HasName("pk_post_tag_links"); - - b.HasIndex("TagsId") - .HasDatabaseName("ix_post_tag_links_tags_id"); - - b.ToTable("post_tag_links", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.AccountAuthFactor", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("AuthFactors") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_auth_factors_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.AccountContact", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Contacts") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_contacts_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.ActionLog", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_action_logs_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Auth.Session", "Session") - .WithMany() - .HasForeignKey("SessionId") - .HasConstraintName("fk_action_logs_auth_sessions_session_id"); - - b.Navigation("Account"); - - b.Navigation("Session"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Badge", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Badges") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_badges_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.CheckInResult", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_check_in_results_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.MagicSpell", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .HasConstraintName("fk_magic_spells_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Notification", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_notifications_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.NotificationPushSubscription", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_notification_push_subscriptions_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Profile", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithOne("Profile") - .HasForeignKey("DysonNetwork.Sphere.Account.Profile", "AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_profiles_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "Background") - .WithMany() - .HasForeignKey("BackgroundId") - .HasConstraintName("fk_account_profiles_files_background_id"); - - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "Picture") - .WithMany() - .HasForeignKey("PictureId") - .HasConstraintName("fk_account_profiles_files_picture_id"); - - b.Navigation("Account"); - - b.Navigation("Background"); - - b.Navigation("Picture"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Relationship", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("OutgoingRelationships") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_relationships_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Account.Account", "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.Sphere.Account.Status", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_statuses_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Activity.Activity", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_activities_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Challenge", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Challenges") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_challenges_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Session", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Sessions") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_sessions_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Auth.Challenge", "Challenge") - .WithMany() - .HasForeignKey("ChallengeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_sessions_auth_challenges_challenge_id"); - - b.Navigation("Account"); - - b.Navigation("Challenge"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatMember", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_members_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatRoom", "ChatRoom") - .WithMany("Members") - .HasForeignKey("ChatRoomId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_members_chat_rooms_chat_room_id"); - - b.Navigation("Account"); - - b.Navigation("ChatRoom"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatRoom", b => - { - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "Background") - .WithMany() - .HasForeignKey("BackgroundId") - .HasConstraintName("fk_chat_rooms_files_background_id"); - - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "Picture") - .WithMany() - .HasForeignKey("PictureId") - .HasConstraintName("fk_chat_rooms_files_picture_id"); - - b.HasOne("DysonNetwork.Sphere.Realm.Realm", "Realm") - .WithMany("ChatRooms") - .HasForeignKey("RealmId") - .HasConstraintName("fk_chat_rooms_realms_realm_id"); - - b.Navigation("Background"); - - b.Navigation("Picture"); - - b.Navigation("Realm"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.Message", b => - { - b.HasOne("DysonNetwork.Sphere.Chat.ChatRoom", "ChatRoom") - .WithMany() - .HasForeignKey("ChatRoomId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_messages_chat_rooms_chat_room_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.Message", "ForwardedMessage") - .WithMany() - .HasForeignKey("ForwardedMessageId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_chat_messages_chat_messages_forwarded_message_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.Message", "RepliedMessage") - .WithMany() - .HasForeignKey("RepliedMessageId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_chat_messages_chat_messages_replied_message_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatMember", "Sender") - .WithMany() - .HasForeignKey("SenderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_messages_chat_members_sender_id"); - - b.Navigation("ChatRoom"); - - b.Navigation("ForwardedMessage"); - - b.Navigation("RepliedMessage"); - - b.Navigation("Sender"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.MessageReaction", b => - { - b.HasOne("DysonNetwork.Sphere.Chat.Message", "Message") - .WithMany("Reactions") - .HasForeignKey("MessageId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_reactions_chat_messages_message_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatMember", "Sender") - .WithMany() - .HasForeignKey("SenderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_reactions_chat_members_sender_id"); - - b.Navigation("Message"); - - b.Navigation("Sender"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.RealtimeCall", b => - { - b.HasOne("DysonNetwork.Sphere.Chat.ChatRoom", "Room") - .WithMany() - .HasForeignKey("RoomId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_realtime_call_chat_rooms_room_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatMember", "Sender") - .WithMany() - .HasForeignKey("SenderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_realtime_call_chat_members_sender_id"); - - b.Navigation("Room"); - - b.Navigation("Sender"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomApp", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Developer") - .WithMany() - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_custom_apps_publishers_publisher_id"); - - b.Navigation("Developer"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomAppSecret", b => - { - b.HasOne("DysonNetwork.Sphere.Developer.CustomApp", "App") - .WithMany() - .HasForeignKey("AppId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_custom_app_secrets_custom_apps_app_id"); - - b.Navigation("App"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroupMember", b => - { - b.HasOne("DysonNetwork.Sphere.Permission.PermissionGroup", "Group") - .WithMany("Members") - .HasForeignKey("GroupId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_permission_group_members_permission_groups_group_id"); - - b.Navigation("Group"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionNode", b => - { - b.HasOne("DysonNetwork.Sphere.Permission.PermissionGroup", "Group") - .WithMany("Nodes") - .HasForeignKey("GroupId") - .HasConstraintName("fk_permission_nodes_permission_groups_group_id"); - - b.Navigation("Group"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.Post", b => - { - b.HasOne("DysonNetwork.Sphere.Post.Post", "ForwardedPost") - .WithMany() - .HasForeignKey("ForwardedPostId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_posts_posts_forwarded_post_id"); - - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Posts") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_posts_publishers_publisher_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", "RepliedPost") - .WithMany() - .HasForeignKey("RepliedPostId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_posts_posts_replied_post_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", "ThreadedPost") - .WithOne() - .HasForeignKey("DysonNetwork.Sphere.Post.Post", "ThreadedPostId") - .HasConstraintName("fk_posts_posts_threaded_post_id"); - - b.Navigation("ForwardedPost"); - - b.Navigation("Publisher"); - - b.Navigation("RepliedPost"); - - b.Navigation("ThreadedPost"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostCollection", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Collections") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_collections_publishers_publisher_id"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostReaction", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_reactions_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", "Post") - .WithMany("Reactions") - .HasForeignKey("PostId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_reactions_posts_post_id"); - - b.Navigation("Account"); - - b.Navigation("Post"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.Publisher", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .HasConstraintName("fk_publishers_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "Background") - .WithMany() - .HasForeignKey("BackgroundId") - .HasConstraintName("fk_publishers_files_background_id"); - - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "Picture") - .WithMany() - .HasForeignKey("PictureId") - .HasConstraintName("fk_publishers_files_picture_id"); - - b.HasOne("DysonNetwork.Sphere.Realm.Realm", "Realm") - .WithMany() - .HasForeignKey("RealmId") - .HasConstraintName("fk_publishers_realms_realm_id"); - - b.Navigation("Account"); - - b.Navigation("Background"); - - b.Navigation("Picture"); - - b.Navigation("Realm"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherFeature", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany() - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_features_publishers_publisher_id"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherMember", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_members_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Members") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_members_publishers_publisher_id"); - - b.Navigation("Account"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherSubscription", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_subscriptions_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Subscriptions") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_subscriptions_publishers_publisher_id"); - - b.Navigation("Account"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.Realm", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_realms_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "Background") - .WithMany() - .HasForeignKey("BackgroundId") - .HasConstraintName("fk_realms_files_background_id"); - - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "Picture") - .WithMany() - .HasForeignKey("PictureId") - .HasConstraintName("fk_realms_files_picture_id"); - - b.Navigation("Account"); - - b.Navigation("Background"); - - b.Navigation("Picture"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.RealmMember", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_realm_members_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Realm.Realm", "Realm") - .WithMany("Members") - .HasForeignKey("RealmId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_realm_members_realms_realm_id"); - - b.Navigation("Account"); - - b.Navigation("Realm"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.Sticker", b => - { - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "Image") - .WithMany() - .HasForeignKey("ImageId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_stickers_files_image_id"); - - b.HasOne("DysonNetwork.Sphere.Sticker.StickerPack", "Pack") - .WithMany() - .HasForeignKey("PackId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_stickers_sticker_packs_pack_id"); - - b.Navigation("Image"); - - b.Navigation("Pack"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.StickerPack", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany() - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_sticker_packs_publishers_publisher_id"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFile", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_files_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.Message", null) - .WithMany("Attachments") - .HasForeignKey("MessageId") - .HasConstraintName("fk_files_chat_messages_message_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany("Attachments") - .HasForeignKey("PostId") - .HasConstraintName("fk_files_posts_post_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Order", b => - { - b.HasOne("DysonNetwork.Sphere.Developer.CustomApp", "IssuerApp") - .WithMany() - .HasForeignKey("IssuerAppId") - .HasConstraintName("fk_payment_orders_custom_apps_issuer_app_id"); - - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "PayeeWallet") - .WithMany() - .HasForeignKey("PayeeWalletId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_payment_orders_wallets_payee_wallet_id"); - - b.HasOne("DysonNetwork.Sphere.Wallet.Transaction", "Transaction") - .WithMany() - .HasForeignKey("TransactionId") - .HasConstraintName("fk_payment_orders_payment_transactions_transaction_id"); - - b.Navigation("IssuerApp"); - - b.Navigation("PayeeWallet"); - - b.Navigation("Transaction"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Transaction", b => - { - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "PayeeWallet") - .WithMany() - .HasForeignKey("PayeeWalletId") - .HasConstraintName("fk_payment_transactions_wallets_payee_wallet_id"); - - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "PayerWallet") - .WithMany() - .HasForeignKey("PayerWalletId") - .HasConstraintName("fk_payment_transactions_wallets_payer_wallet_id"); - - b.Navigation("PayeeWallet"); - - b.Navigation("PayerWallet"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Wallet", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_wallets_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.WalletPocket", b => - { - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "Wallet") - .WithMany("Pockets") - .HasForeignKey("WalletId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_wallet_pockets_wallets_wallet_id"); - - b.Navigation("Wallet"); - }); - - modelBuilder.Entity("PostPostCategory", b => - { - b.HasOne("DysonNetwork.Sphere.Post.PostCategory", null) - .WithMany() - .HasForeignKey("CategoriesId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_category_links_post_categories_categories_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany() - .HasForeignKey("PostsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_category_links_posts_posts_id"); - }); - - modelBuilder.Entity("PostPostCollection", b => - { - b.HasOne("DysonNetwork.Sphere.Post.PostCollection", null) - .WithMany() - .HasForeignKey("CollectionsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_collection_links_post_collections_collections_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany() - .HasForeignKey("PostsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_collection_links_posts_posts_id"); - }); - - modelBuilder.Entity("PostPostTag", b => - { - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany() - .HasForeignKey("PostsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_tag_links_posts_posts_id"); - - b.HasOne("DysonNetwork.Sphere.Post.PostTag", null) - .WithMany() - .HasForeignKey("TagsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_tag_links_post_tags_tags_id"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Account", b => - { - b.Navigation("AuthFactors"); - - b.Navigation("Badges"); - - b.Navigation("Challenges"); - - b.Navigation("Contacts"); - - b.Navigation("IncomingRelationships"); - - b.Navigation("OutgoingRelationships"); - - b.Navigation("Profile") - .IsRequired(); - - b.Navigation("Sessions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatRoom", b => - { - b.Navigation("Members"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.Message", b => - { - b.Navigation("Attachments"); - - b.Navigation("Reactions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroup", b => - { - b.Navigation("Members"); - - b.Navigation("Nodes"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.Post", b => - { - b.Navigation("Attachments"); - - b.Navigation("Reactions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.Publisher", b => - { - b.Navigation("Collections"); - - b.Navigation("Members"); - - b.Navigation("Posts"); - - b.Navigation("Subscriptions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.Realm", b => - { - b.Navigation("ChatRooms"); - - b.Navigation("Members"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Wallet", b => - { - b.Navigation("Pockets"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/DysonNetwork.Sphere/Migrations/20250525083412_ModifyRelationshipStatusType.cs b/DysonNetwork.Sphere/Migrations/20250525083412_ModifyRelationshipStatusType.cs deleted file mode 100644 index 18cc8b4..0000000 --- a/DysonNetwork.Sphere/Migrations/20250525083412_ModifyRelationshipStatusType.cs +++ /dev/null @@ -1,34 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace DysonNetwork.Sphere.Migrations -{ - /// - public partial class ModifyRelationshipStatusType : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AlterColumn( - name: "status", - table: "account_relationships", - type: "smallint", - nullable: false, - oldClrType: typeof(int), - oldType: "integer"); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.AlterColumn( - name: "status", - table: "account_relationships", - type: "integer", - nullable: false, - oldClrType: typeof(short), - oldType: "smallint"); - } - } -} diff --git a/DysonNetwork.Sphere/Migrations/20250527144902_LimitedSizeForPictureIdOnPub.Designer.cs b/DysonNetwork.Sphere/Migrations/20250527144902_LimitedSizeForPictureIdOnPub.Designer.cs deleted file mode 100644 index fd69a6e..0000000 --- a/DysonNetwork.Sphere/Migrations/20250527144902_LimitedSizeForPictureIdOnPub.Designer.cs +++ /dev/null @@ -1,3402 +0,0 @@ -// -using System; -using System.Collections.Generic; -using System.Text.Json; -using DysonNetwork.Sphere; -using DysonNetwork.Sphere.Account; -using DysonNetwork.Sphere.Storage; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using NetTopologySuite.Geometries; -using NodaTime; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; -using NpgsqlTypes; - -#nullable disable - -namespace DysonNetwork.Sphere.Migrations -{ - [DbContext(typeof(AppDatabase))] - [Migration("20250527144902_LimitedSizeForPictureIdOnPub")] - partial class LimitedSizeForPictureIdOnPub - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "9.0.3") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "postgis"); - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Account", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("ActivatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("activated_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("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("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.Sphere.Account.AccountAuthFactor", 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("Secret") - .HasMaxLength(8196) - .HasColumnType("character varying(8196)") - .HasColumnName("secret"); - - 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.Sphere.Account.AccountContact", 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("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.Sphere.Account.ActionLog", 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("geometry") - .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.HasIndex("SessionId") - .HasDatabaseName("ix_action_logs_session_id"); - - b.ToTable("action_logs", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Badge", 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("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.Sphere.Account.CheckInResult", 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("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.Sphere.Account.MagicSpell", 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.Sphere.Account.Notification", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("Content") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .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>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("Priority") - .HasColumnType("integer") - .HasColumnName("priority"); - - b.Property("Subtitle") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("subtitle"); - - b.Property("Title") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("title"); - - b.Property("Topic") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("topic"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("ViewedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("viewed_at"); - - b.HasKey("Id") - .HasName("pk_notifications"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_notifications_account_id"); - - b.ToTable("notifications", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.NotificationPushSubscription", 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(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("device_id"); - - b.Property("DeviceToken") - .IsRequired() - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("device_token"); - - b.Property("LastUsedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_used_at"); - - b.Property("Provider") - .HasColumnType("integer") - .HasColumnName("provider"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_notification_push_subscriptions"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_notification_push_subscriptions_account_id"); - - b.HasIndex("DeviceToken", "DeviceId") - .IsUnique() - .HasDatabaseName("ix_notification_push_subscriptions_device_token_device_id"); - - b.ToTable("notification_push_subscriptions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Profile", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_id"); - - 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("MiddleName") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("middle_name"); - - b.Property("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - b.Property("Pronouns") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("pronouns"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_account_profiles"); - - b.HasIndex("AccountId") - .IsUnique() - .HasDatabaseName("ix_account_profiles_account_id"); - - b.HasIndex("BackgroundId") - .HasDatabaseName("ix_account_profiles_background_id"); - - b.HasIndex("PictureId") - .HasDatabaseName("ix_account_profiles_picture_id"); - - b.ToTable("account_profiles", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Relationship", 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.Sphere.Account.Status", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - 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("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("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.Sphere.Activity.Activity", 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>("Meta") - .IsRequired() - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("ResourceIdentifier") - .IsRequired() - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("resource_identifier"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property>("UsersVisible") - .IsRequired() - .HasColumnType("jsonb") - .HasColumnName("users_visible"); - - b.Property("Visibility") - .HasColumnType("integer") - .HasColumnName("visibility"); - - b.HasKey("Id") - .HasName("pk_activities"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_activities_account_id"); - - b.ToTable("activities", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Challenge", 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") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("device_id"); - - 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("geometry") - .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("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_challenges"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_auth_challenges_account_id"); - - b.ToTable("auth_challenges", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Session", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("ChallengeId") - .HasColumnType("uuid") - .HasColumnName("challenge_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("Label") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("label"); - - b.Property("LastGrantedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_granted_at"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - 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.ToTable("auth_sessions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatMember", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("ChatRoomId") - .HasColumnType("uuid") - .HasColumnName("chat_room_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("IsBot") - .HasColumnType("boolean") - .HasColumnName("is_bot"); - - b.Property("JoinedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("joined_at"); - - b.Property("LastReadAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_read_at"); - - b.Property("LeaveAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("leave_at"); - - b.Property("Nick") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("nick"); - - b.Property("Notify") - .HasColumnType("integer") - .HasColumnName("notify"); - - b.Property("Role") - .HasColumnType("integer") - .HasColumnName("role"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_chat_members"); - - b.HasAlternateKey("ChatRoomId", "AccountId") - .HasName("ak_chat_members_chat_room_id_account_id"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_chat_members_account_id"); - - b.ToTable("chat_members", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatRoom", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_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("Description") - .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") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - b.Property("RealmId") - .HasColumnType("uuid") - .HasColumnName("realm_id"); - - b.Property("Type") - .HasColumnType("integer") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_chat_rooms"); - - b.HasIndex("BackgroundId") - .HasDatabaseName("ix_chat_rooms_background_id"); - - b.HasIndex("PictureId") - .HasDatabaseName("ix_chat_rooms_picture_id"); - - b.HasIndex("RealmId") - .HasDatabaseName("ix_chat_rooms_realm_id"); - - b.ToTable("chat_rooms", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.Message", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("ChatRoomId") - .HasColumnType("uuid") - .HasColumnName("chat_room_id"); - - b.Property("Content") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .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("EditedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("edited_at"); - - b.Property("ForwardedMessageId") - .HasColumnType("uuid") - .HasColumnName("forwarded_message_id"); - - b.Property>("MembersMentioned") - .HasColumnType("jsonb") - .HasColumnName("members_mentioned"); - - b.Property>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("Nonce") - .IsRequired() - .HasMaxLength(36) - .HasColumnType("character varying(36)") - .HasColumnName("nonce"); - - b.Property("RepliedMessageId") - .HasColumnType("uuid") - .HasColumnName("replied_message_id"); - - b.Property("SenderId") - .HasColumnType("uuid") - .HasColumnName("sender_id"); - - 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_chat_messages"); - - b.HasIndex("ChatRoomId") - .HasDatabaseName("ix_chat_messages_chat_room_id"); - - b.HasIndex("ForwardedMessageId") - .HasDatabaseName("ix_chat_messages_forwarded_message_id"); - - b.HasIndex("RepliedMessageId") - .HasDatabaseName("ix_chat_messages_replied_message_id"); - - b.HasIndex("SenderId") - .HasDatabaseName("ix_chat_messages_sender_id"); - - b.ToTable("chat_messages", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.MessageReaction", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("Attitude") - .HasColumnType("integer") - .HasColumnName("attitude"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("MessageId") - .HasColumnType("uuid") - .HasColumnName("message_id"); - - b.Property("SenderId") - .HasColumnType("uuid") - .HasColumnName("sender_id"); - - b.Property("Symbol") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("symbol"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_chat_reactions"); - - b.HasIndex("MessageId") - .HasDatabaseName("ix_chat_reactions_message_id"); - - b.HasIndex("SenderId") - .HasDatabaseName("ix_chat_reactions_sender_id"); - - b.ToTable("chat_reactions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.RealtimeCall", 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("EndedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("ended_at"); - - b.Property("ProviderName") - .HasColumnType("text") - .HasColumnName("provider_name"); - - b.Property("RoomId") - .HasColumnType("uuid") - .HasColumnName("room_id"); - - b.Property("SenderId") - .HasColumnType("uuid") - .HasColumnName("sender_id"); - - b.Property("SessionId") - .HasColumnType("text") - .HasColumnName("session_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("UpstreamConfigJson") - .HasColumnType("jsonb") - .HasColumnName("upstream"); - - b.HasKey("Id") - .HasName("pk_chat_realtime_call"); - - b.HasIndex("RoomId") - .HasDatabaseName("ix_chat_realtime_call_room_id"); - - b.HasIndex("SenderId") - .HasDatabaseName("ix_chat_realtime_call_sender_id"); - - b.ToTable("chat_realtime_call", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomApp", 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("Name") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("slug"); - - b.Property("Status") - .HasColumnType("integer") - .HasColumnName("status"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("VerifiedAs") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("verified_as"); - - b.Property("VerifiedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("verified_at"); - - b.HasKey("Id") - .HasName("pk_custom_apps"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_custom_apps_publisher_id"); - - b.ToTable("custom_apps", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomAppSecret", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AppId") - .HasColumnType("uuid") - .HasColumnName("app_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("Remarks") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("remarks"); - - b.Property("Secret") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("secret"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_custom_app_secrets"); - - b.HasIndex("AppId") - .HasDatabaseName("ix_custom_app_secrets_app_id"); - - b.ToTable("custom_app_secrets", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroup", 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.Sphere.Permission.PermissionGroupMember", 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.Sphere.Permission.PermissionNode", 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("Area") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("area"); - - 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("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", "Area", "Actor") - .HasDatabaseName("ix_permission_nodes_key_area_actor"); - - b.ToTable("permission_nodes", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.Post", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("Content") - .HasColumnType("text") - .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("Description") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("Downvotes") - .HasColumnType("integer") - .HasColumnName("downvotes"); - - b.Property("EditedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("edited_at"); - - b.Property("ForwardedPostId") - .HasColumnType("uuid") - .HasColumnName("forwarded_post_id"); - - b.Property("Language") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("language"); - - b.Property>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("PublishedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("published_at"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("RepliedPostId") - .HasColumnType("uuid") - .HasColumnName("replied_post_id"); - - b.Property("SearchVector") - .IsRequired() - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("tsvector") - .HasColumnName("search_vector") - .HasAnnotation("Npgsql:TsVectorConfig", "simple") - .HasAnnotation("Npgsql:TsVectorProperties", new[] { "Title", "Description", "Content" }); - - b.Property("ThreadedPostId") - .HasColumnType("uuid") - .HasColumnName("threaded_post_id"); - - b.Property("Title") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("title"); - - b.Property("Type") - .HasColumnType("integer") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("Upvotes") - .HasColumnType("integer") - .HasColumnName("upvotes"); - - b.Property("ViewsTotal") - .HasColumnType("integer") - .HasColumnName("views_total"); - - b.Property("ViewsUnique") - .HasColumnType("integer") - .HasColumnName("views_unique"); - - b.Property("Visibility") - .HasColumnType("integer") - .HasColumnName("visibility"); - - b.HasKey("Id") - .HasName("pk_posts"); - - b.HasIndex("ForwardedPostId") - .HasDatabaseName("ix_posts_forwarded_post_id"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_posts_publisher_id"); - - b.HasIndex("RepliedPostId") - .HasDatabaseName("ix_posts_replied_post_id"); - - b.HasIndex("SearchVector") - .HasDatabaseName("ix_posts_search_vector"); - - NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex("SearchVector"), "GIN"); - - b.HasIndex("ThreadedPostId") - .IsUnique() - .HasDatabaseName("ix_posts_threaded_post_id"); - - b.ToTable("posts", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostCategory", 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("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("name"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_categories"); - - b.ToTable("post_categories", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostCollection", 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("Description") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("name"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_collections"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_post_collections_publisher_id"); - - b.ToTable("post_collections", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostReaction", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("Attitude") - .HasColumnType("integer") - .HasColumnName("attitude"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("PostId") - .HasColumnType("uuid") - .HasColumnName("post_id"); - - b.Property("Symbol") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("symbol"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_reactions"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_post_reactions_account_id"); - - b.HasIndex("PostId") - .HasDatabaseName("ix_post_reactions_post_id"); - - b.ToTable("post_reactions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostTag", 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("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("name"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_tags"); - - b.ToTable("post_tags", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.Publisher", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_id"); - - b.Property("Bio") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("bio"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - 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("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - b.Property("RealmId") - .HasColumnType("uuid") - .HasColumnName("realm_id"); - - b.Property("Type") - .HasColumnType("integer") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_publishers"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_publishers_account_id"); - - b.HasIndex("BackgroundId") - .HasDatabaseName("ix_publishers_background_id"); - - b.HasIndex("Name") - .IsUnique() - .HasDatabaseName("ix_publishers_name"); - - b.HasIndex("PictureId") - .HasDatabaseName("ix_publishers_picture_id"); - - b.HasIndex("RealmId") - .HasDatabaseName("ix_publishers_realm_id"); - - b.ToTable("publishers", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherFeature", 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("ExpiredAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expired_at"); - - b.Property("Flag") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("flag"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_publisher_features"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_publisher_features_publisher_id"); - - b.ToTable("publisher_features", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherMember", b => - { - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_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("Role") - .HasColumnType("integer") - .HasColumnName("role"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("PublisherId", "AccountId") - .HasName("pk_publisher_members"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_publisher_members_account_id"); - - b.ToTable("publisher_members", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherSubscription", 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("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("Status") - .HasColumnType("integer") - .HasColumnName("status"); - - b.Property("Tier") - .HasColumnType("integer") - .HasColumnName("tier"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_publisher_subscriptions"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_publisher_subscriptions_account_id"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_publisher_subscriptions_publisher_id"); - - b.ToTable("publisher_subscriptions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.Realm", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_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("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("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - 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("VerifiedAs") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("verified_as"); - - b.Property("VerifiedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("verified_at"); - - b.HasKey("Id") - .HasName("pk_realms"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_realms_account_id"); - - b.HasIndex("BackgroundId") - .HasDatabaseName("ix_realms_background_id"); - - b.HasIndex("PictureId") - .HasDatabaseName("ix_realms_picture_id"); - - b.HasIndex("Slug") - .IsUnique() - .HasDatabaseName("ix_realms_slug"); - - b.ToTable("realms", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.RealmMember", 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.HasIndex("AccountId") - .HasDatabaseName("ix_realm_members_account_id"); - - b.ToTable("realm_members", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.Sticker", 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("ImageId") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("image_id"); - - b.Property("PackId") - .HasColumnType("uuid") - .HasColumnName("pack_id"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_stickers"); - - b.HasIndex("ImageId") - .HasDatabaseName("ix_stickers_image_id"); - - b.HasIndex("PackId") - .HasDatabaseName("ix_stickers_pack_id"); - - b.HasIndex("Slug") - .HasDatabaseName("ix_stickers_slug"); - - b.ToTable("stickers", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.StickerPack", 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("Description") - .IsRequired() - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("Prefix") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("prefix"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_sticker_packs"); - - b.HasIndex("Prefix") - .IsUnique() - .HasDatabaseName("ix_sticker_packs_prefix"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_sticker_packs_publisher_id"); - - b.ToTable("sticker_packs", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFile", b => - { - b.Property("Id") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .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("Description") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("ExpiredAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expired_at"); - - b.Property>("FileMeta") - .HasColumnType("jsonb") - .HasColumnName("file_meta"); - - b.Property("HasCompression") - .HasColumnType("boolean") - .HasColumnName("has_compression"); - - b.Property("Hash") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("hash"); - - b.Property("MessageId") - .HasColumnType("uuid") - .HasColumnName("message_id"); - - b.Property("MimeType") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("mime_type"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("PostId") - .HasColumnType("uuid") - .HasColumnName("post_id"); - - b.Property>("SensitiveMarks") - .HasColumnType("jsonb") - .HasColumnName("sensitive_marks"); - - b.Property("Size") - .HasColumnType("bigint") - .HasColumnName("size"); - - b.Property("StorageId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("storage_id"); - - b.Property("StorageUrl") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("storage_url"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("UploadedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("uploaded_at"); - - b.Property("UploadedTo") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("uploaded_to"); - - b.Property("UsedCount") - .HasColumnType("integer") - .HasColumnName("used_count"); - - b.Property>("UserMeta") - .HasColumnType("jsonb") - .HasColumnName("user_meta"); - - b.HasKey("Id") - .HasName("pk_files"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_files_account_id"); - - b.HasIndex("MessageId") - .HasDatabaseName("ix_files_message_id"); - - b.HasIndex("PostId") - .HasDatabaseName("ix_files_post_id"); - - b.ToTable("files", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Order", 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("ExpiredAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expired_at"); - - b.Property("IssuerAppId") - .HasColumnType("uuid") - .HasColumnName("issuer_app_id"); - - b.Property("PayeeWalletId") - .HasColumnType("uuid") - .HasColumnName("payee_wallet_id"); - - 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("IssuerAppId") - .HasDatabaseName("ix_payment_orders_issuer_app_id"); - - 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.Sphere.Wallet.Transaction", 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.Sphere.Wallet.Wallet", 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.Sphere.Wallet.WalletPocket", 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("PostPostCategory", b => - { - b.Property("CategoriesId") - .HasColumnType("uuid") - .HasColumnName("categories_id"); - - b.Property("PostsId") - .HasColumnType("uuid") - .HasColumnName("posts_id"); - - b.HasKey("CategoriesId", "PostsId") - .HasName("pk_post_category_links"); - - b.HasIndex("PostsId") - .HasDatabaseName("ix_post_category_links_posts_id"); - - b.ToTable("post_category_links", (string)null); - }); - - modelBuilder.Entity("PostPostCollection", b => - { - b.Property("CollectionsId") - .HasColumnType("uuid") - .HasColumnName("collections_id"); - - b.Property("PostsId") - .HasColumnType("uuid") - .HasColumnName("posts_id"); - - b.HasKey("CollectionsId", "PostsId") - .HasName("pk_post_collection_links"); - - b.HasIndex("PostsId") - .HasDatabaseName("ix_post_collection_links_posts_id"); - - b.ToTable("post_collection_links", (string)null); - }); - - modelBuilder.Entity("PostPostTag", b => - { - b.Property("PostsId") - .HasColumnType("uuid") - .HasColumnName("posts_id"); - - b.Property("TagsId") - .HasColumnType("uuid") - .HasColumnName("tags_id"); - - b.HasKey("PostsId", "TagsId") - .HasName("pk_post_tag_links"); - - b.HasIndex("TagsId") - .HasDatabaseName("ix_post_tag_links_tags_id"); - - b.ToTable("post_tag_links", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.AccountAuthFactor", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("AuthFactors") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_auth_factors_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.AccountContact", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Contacts") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_contacts_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.ActionLog", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_action_logs_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Auth.Session", "Session") - .WithMany() - .HasForeignKey("SessionId") - .HasConstraintName("fk_action_logs_auth_sessions_session_id"); - - b.Navigation("Account"); - - b.Navigation("Session"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Badge", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Badges") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_badges_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.CheckInResult", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_check_in_results_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.MagicSpell", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .HasConstraintName("fk_magic_spells_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Notification", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_notifications_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.NotificationPushSubscription", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_notification_push_subscriptions_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Profile", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithOne("Profile") - .HasForeignKey("DysonNetwork.Sphere.Account.Profile", "AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_profiles_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "Background") - .WithMany() - .HasForeignKey("BackgroundId") - .HasConstraintName("fk_account_profiles_files_background_id"); - - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "Picture") - .WithMany() - .HasForeignKey("PictureId") - .HasConstraintName("fk_account_profiles_files_picture_id"); - - b.Navigation("Account"); - - b.Navigation("Background"); - - b.Navigation("Picture"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Relationship", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("OutgoingRelationships") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_relationships_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Account.Account", "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.Sphere.Account.Status", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_statuses_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Activity.Activity", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_activities_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Challenge", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Challenges") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_challenges_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Session", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Sessions") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_sessions_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Auth.Challenge", "Challenge") - .WithMany() - .HasForeignKey("ChallengeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_sessions_auth_challenges_challenge_id"); - - b.Navigation("Account"); - - b.Navigation("Challenge"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatMember", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_members_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatRoom", "ChatRoom") - .WithMany("Members") - .HasForeignKey("ChatRoomId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_members_chat_rooms_chat_room_id"); - - b.Navigation("Account"); - - b.Navigation("ChatRoom"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatRoom", b => - { - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "Background") - .WithMany() - .HasForeignKey("BackgroundId") - .HasConstraintName("fk_chat_rooms_files_background_id"); - - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "Picture") - .WithMany() - .HasForeignKey("PictureId") - .HasConstraintName("fk_chat_rooms_files_picture_id"); - - b.HasOne("DysonNetwork.Sphere.Realm.Realm", "Realm") - .WithMany("ChatRooms") - .HasForeignKey("RealmId") - .HasConstraintName("fk_chat_rooms_realms_realm_id"); - - b.Navigation("Background"); - - b.Navigation("Picture"); - - b.Navigation("Realm"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.Message", b => - { - b.HasOne("DysonNetwork.Sphere.Chat.ChatRoom", "ChatRoom") - .WithMany() - .HasForeignKey("ChatRoomId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_messages_chat_rooms_chat_room_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.Message", "ForwardedMessage") - .WithMany() - .HasForeignKey("ForwardedMessageId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_chat_messages_chat_messages_forwarded_message_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.Message", "RepliedMessage") - .WithMany() - .HasForeignKey("RepliedMessageId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_chat_messages_chat_messages_replied_message_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatMember", "Sender") - .WithMany() - .HasForeignKey("SenderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_messages_chat_members_sender_id"); - - b.Navigation("ChatRoom"); - - b.Navigation("ForwardedMessage"); - - b.Navigation("RepliedMessage"); - - b.Navigation("Sender"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.MessageReaction", b => - { - b.HasOne("DysonNetwork.Sphere.Chat.Message", "Message") - .WithMany("Reactions") - .HasForeignKey("MessageId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_reactions_chat_messages_message_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatMember", "Sender") - .WithMany() - .HasForeignKey("SenderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_reactions_chat_members_sender_id"); - - b.Navigation("Message"); - - b.Navigation("Sender"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.RealtimeCall", b => - { - b.HasOne("DysonNetwork.Sphere.Chat.ChatRoom", "Room") - .WithMany() - .HasForeignKey("RoomId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_realtime_call_chat_rooms_room_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatMember", "Sender") - .WithMany() - .HasForeignKey("SenderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_realtime_call_chat_members_sender_id"); - - b.Navigation("Room"); - - b.Navigation("Sender"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomApp", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Developer") - .WithMany() - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_custom_apps_publishers_publisher_id"); - - b.Navigation("Developer"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomAppSecret", b => - { - b.HasOne("DysonNetwork.Sphere.Developer.CustomApp", "App") - .WithMany() - .HasForeignKey("AppId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_custom_app_secrets_custom_apps_app_id"); - - b.Navigation("App"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroupMember", b => - { - b.HasOne("DysonNetwork.Sphere.Permission.PermissionGroup", "Group") - .WithMany("Members") - .HasForeignKey("GroupId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_permission_group_members_permission_groups_group_id"); - - b.Navigation("Group"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionNode", b => - { - b.HasOne("DysonNetwork.Sphere.Permission.PermissionGroup", "Group") - .WithMany("Nodes") - .HasForeignKey("GroupId") - .HasConstraintName("fk_permission_nodes_permission_groups_group_id"); - - b.Navigation("Group"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.Post", b => - { - b.HasOne("DysonNetwork.Sphere.Post.Post", "ForwardedPost") - .WithMany() - .HasForeignKey("ForwardedPostId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_posts_posts_forwarded_post_id"); - - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Posts") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_posts_publishers_publisher_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", "RepliedPost") - .WithMany() - .HasForeignKey("RepliedPostId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_posts_posts_replied_post_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", "ThreadedPost") - .WithOne() - .HasForeignKey("DysonNetwork.Sphere.Post.Post", "ThreadedPostId") - .HasConstraintName("fk_posts_posts_threaded_post_id"); - - b.Navigation("ForwardedPost"); - - b.Navigation("Publisher"); - - b.Navigation("RepliedPost"); - - b.Navigation("ThreadedPost"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostCollection", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Collections") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_collections_publishers_publisher_id"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostReaction", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_reactions_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", "Post") - .WithMany("Reactions") - .HasForeignKey("PostId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_reactions_posts_post_id"); - - b.Navigation("Account"); - - b.Navigation("Post"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.Publisher", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .HasConstraintName("fk_publishers_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "Background") - .WithMany() - .HasForeignKey("BackgroundId") - .HasConstraintName("fk_publishers_files_background_id"); - - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "Picture") - .WithMany() - .HasForeignKey("PictureId") - .HasConstraintName("fk_publishers_files_picture_id"); - - b.HasOne("DysonNetwork.Sphere.Realm.Realm", "Realm") - .WithMany() - .HasForeignKey("RealmId") - .HasConstraintName("fk_publishers_realms_realm_id"); - - b.Navigation("Account"); - - b.Navigation("Background"); - - b.Navigation("Picture"); - - b.Navigation("Realm"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherFeature", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany() - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_features_publishers_publisher_id"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherMember", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_members_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Members") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_members_publishers_publisher_id"); - - b.Navigation("Account"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherSubscription", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_subscriptions_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Subscriptions") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_subscriptions_publishers_publisher_id"); - - b.Navigation("Account"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.Realm", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_realms_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "Background") - .WithMany() - .HasForeignKey("BackgroundId") - .HasConstraintName("fk_realms_files_background_id"); - - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "Picture") - .WithMany() - .HasForeignKey("PictureId") - .HasConstraintName("fk_realms_files_picture_id"); - - b.Navigation("Account"); - - b.Navigation("Background"); - - b.Navigation("Picture"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.RealmMember", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_realm_members_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Realm.Realm", "Realm") - .WithMany("Members") - .HasForeignKey("RealmId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_realm_members_realms_realm_id"); - - b.Navigation("Account"); - - b.Navigation("Realm"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.Sticker", b => - { - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "Image") - .WithMany() - .HasForeignKey("ImageId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_stickers_files_image_id"); - - b.HasOne("DysonNetwork.Sphere.Sticker.StickerPack", "Pack") - .WithMany() - .HasForeignKey("PackId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_stickers_sticker_packs_pack_id"); - - b.Navigation("Image"); - - b.Navigation("Pack"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.StickerPack", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany() - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_sticker_packs_publishers_publisher_id"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFile", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_files_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.Message", null) - .WithMany("Attachments") - .HasForeignKey("MessageId") - .HasConstraintName("fk_files_chat_messages_message_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany("Attachments") - .HasForeignKey("PostId") - .HasConstraintName("fk_files_posts_post_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Order", b => - { - b.HasOne("DysonNetwork.Sphere.Developer.CustomApp", "IssuerApp") - .WithMany() - .HasForeignKey("IssuerAppId") - .HasConstraintName("fk_payment_orders_custom_apps_issuer_app_id"); - - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "PayeeWallet") - .WithMany() - .HasForeignKey("PayeeWalletId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_payment_orders_wallets_payee_wallet_id"); - - b.HasOne("DysonNetwork.Sphere.Wallet.Transaction", "Transaction") - .WithMany() - .HasForeignKey("TransactionId") - .HasConstraintName("fk_payment_orders_payment_transactions_transaction_id"); - - b.Navigation("IssuerApp"); - - b.Navigation("PayeeWallet"); - - b.Navigation("Transaction"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Transaction", b => - { - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "PayeeWallet") - .WithMany() - .HasForeignKey("PayeeWalletId") - .HasConstraintName("fk_payment_transactions_wallets_payee_wallet_id"); - - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "PayerWallet") - .WithMany() - .HasForeignKey("PayerWalletId") - .HasConstraintName("fk_payment_transactions_wallets_payer_wallet_id"); - - b.Navigation("PayeeWallet"); - - b.Navigation("PayerWallet"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Wallet", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_wallets_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.WalletPocket", b => - { - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "Wallet") - .WithMany("Pockets") - .HasForeignKey("WalletId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_wallet_pockets_wallets_wallet_id"); - - b.Navigation("Wallet"); - }); - - modelBuilder.Entity("PostPostCategory", b => - { - b.HasOne("DysonNetwork.Sphere.Post.PostCategory", null) - .WithMany() - .HasForeignKey("CategoriesId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_category_links_post_categories_categories_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany() - .HasForeignKey("PostsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_category_links_posts_posts_id"); - }); - - modelBuilder.Entity("PostPostCollection", b => - { - b.HasOne("DysonNetwork.Sphere.Post.PostCollection", null) - .WithMany() - .HasForeignKey("CollectionsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_collection_links_post_collections_collections_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany() - .HasForeignKey("PostsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_collection_links_posts_posts_id"); - }); - - modelBuilder.Entity("PostPostTag", b => - { - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany() - .HasForeignKey("PostsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_tag_links_posts_posts_id"); - - b.HasOne("DysonNetwork.Sphere.Post.PostTag", null) - .WithMany() - .HasForeignKey("TagsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_tag_links_post_tags_tags_id"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Account", b => - { - b.Navigation("AuthFactors"); - - b.Navigation("Badges"); - - b.Navigation("Challenges"); - - b.Navigation("Contacts"); - - b.Navigation("IncomingRelationships"); - - b.Navigation("OutgoingRelationships"); - - b.Navigation("Profile") - .IsRequired(); - - b.Navigation("Sessions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatRoom", b => - { - b.Navigation("Members"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.Message", b => - { - b.Navigation("Attachments"); - - b.Navigation("Reactions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroup", b => - { - b.Navigation("Members"); - - b.Navigation("Nodes"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.Post", b => - { - b.Navigation("Attachments"); - - b.Navigation("Reactions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.Publisher", b => - { - b.Navigation("Collections"); - - b.Navigation("Members"); - - b.Navigation("Posts"); - - b.Navigation("Subscriptions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.Realm", b => - { - b.Navigation("ChatRooms"); - - b.Navigation("Members"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Wallet", b => - { - b.Navigation("Pockets"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/DysonNetwork.Sphere/Migrations/20250527144902_LimitedSizeForPictureIdOnPub.cs b/DysonNetwork.Sphere/Migrations/20250527144902_LimitedSizeForPictureIdOnPub.cs deleted file mode 100644 index 3f85ea3..0000000 --- a/DysonNetwork.Sphere/Migrations/20250527144902_LimitedSizeForPictureIdOnPub.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace DysonNetwork.Sphere.Migrations -{ - /// - public partial class LimitedSizeForPictureIdOnPub : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - - } - } -} diff --git a/DysonNetwork.Sphere/Migrations/20250528171935_AddCloudFileUsage.Designer.cs b/DysonNetwork.Sphere/Migrations/20250528171935_AddCloudFileUsage.Designer.cs deleted file mode 100644 index 3ced012..0000000 --- a/DysonNetwork.Sphere/Migrations/20250528171935_AddCloudFileUsage.Designer.cs +++ /dev/null @@ -1,3407 +0,0 @@ -// -using System; -using System.Collections.Generic; -using System.Text.Json; -using DysonNetwork.Sphere; -using DysonNetwork.Sphere.Account; -using DysonNetwork.Sphere.Storage; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using NetTopologySuite.Geometries; -using NodaTime; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; -using NpgsqlTypes; - -#nullable disable - -namespace DysonNetwork.Sphere.Migrations -{ - [DbContext(typeof(AppDatabase))] - [Migration("20250528171935_AddCloudFileUsage")] - partial class AddCloudFileUsage - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "9.0.3") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "postgis"); - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Account", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("ActivatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("activated_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("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("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.Sphere.Account.AccountAuthFactor", 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("Secret") - .HasMaxLength(8196) - .HasColumnType("character varying(8196)") - .HasColumnName("secret"); - - 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.Sphere.Account.AccountContact", 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("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.Sphere.Account.ActionLog", 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("geometry") - .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.HasIndex("SessionId") - .HasDatabaseName("ix_action_logs_session_id"); - - b.ToTable("action_logs", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Badge", 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("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.Sphere.Account.CheckInResult", 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("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.Sphere.Account.MagicSpell", 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.Sphere.Account.Notification", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("Content") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .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>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("Priority") - .HasColumnType("integer") - .HasColumnName("priority"); - - b.Property("Subtitle") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("subtitle"); - - b.Property("Title") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("title"); - - b.Property("Topic") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("topic"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("ViewedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("viewed_at"); - - b.HasKey("Id") - .HasName("pk_notifications"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_notifications_account_id"); - - b.ToTable("notifications", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.NotificationPushSubscription", 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(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("device_id"); - - b.Property("DeviceToken") - .IsRequired() - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("device_token"); - - b.Property("LastUsedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_used_at"); - - b.Property("Provider") - .HasColumnType("integer") - .HasColumnName("provider"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_notification_push_subscriptions"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_notification_push_subscriptions_account_id"); - - b.HasIndex("DeviceToken", "DeviceId") - .IsUnique() - .HasDatabaseName("ix_notification_push_subscriptions_device_token_device_id"); - - b.ToTable("notification_push_subscriptions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Profile", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_id"); - - 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("MiddleName") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("middle_name"); - - b.Property("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - b.Property("Pronouns") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("pronouns"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_account_profiles"); - - b.HasIndex("AccountId") - .IsUnique() - .HasDatabaseName("ix_account_profiles_account_id"); - - b.HasIndex("BackgroundId") - .HasDatabaseName("ix_account_profiles_background_id"); - - b.HasIndex("PictureId") - .HasDatabaseName("ix_account_profiles_picture_id"); - - b.ToTable("account_profiles", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Relationship", 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.Sphere.Account.Status", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - 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("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("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.Sphere.Activity.Activity", 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>("Meta") - .IsRequired() - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("ResourceIdentifier") - .IsRequired() - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("resource_identifier"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property>("UsersVisible") - .IsRequired() - .HasColumnType("jsonb") - .HasColumnName("users_visible"); - - b.Property("Visibility") - .HasColumnType("integer") - .HasColumnName("visibility"); - - b.HasKey("Id") - .HasName("pk_activities"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_activities_account_id"); - - b.ToTable("activities", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Challenge", 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") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("device_id"); - - 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("geometry") - .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("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_challenges"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_auth_challenges_account_id"); - - b.ToTable("auth_challenges", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Session", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("ChallengeId") - .HasColumnType("uuid") - .HasColumnName("challenge_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("Label") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("label"); - - b.Property("LastGrantedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_granted_at"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - 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.ToTable("auth_sessions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatMember", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("ChatRoomId") - .HasColumnType("uuid") - .HasColumnName("chat_room_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("IsBot") - .HasColumnType("boolean") - .HasColumnName("is_bot"); - - b.Property("JoinedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("joined_at"); - - b.Property("LastReadAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_read_at"); - - b.Property("LeaveAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("leave_at"); - - b.Property("Nick") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("nick"); - - b.Property("Notify") - .HasColumnType("integer") - .HasColumnName("notify"); - - b.Property("Role") - .HasColumnType("integer") - .HasColumnName("role"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_chat_members"); - - b.HasAlternateKey("ChatRoomId", "AccountId") - .HasName("ak_chat_members_chat_room_id_account_id"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_chat_members_account_id"); - - b.ToTable("chat_members", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatRoom", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_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("Description") - .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") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - b.Property("RealmId") - .HasColumnType("uuid") - .HasColumnName("realm_id"); - - b.Property("Type") - .HasColumnType("integer") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_chat_rooms"); - - b.HasIndex("BackgroundId") - .HasDatabaseName("ix_chat_rooms_background_id"); - - b.HasIndex("PictureId") - .HasDatabaseName("ix_chat_rooms_picture_id"); - - b.HasIndex("RealmId") - .HasDatabaseName("ix_chat_rooms_realm_id"); - - b.ToTable("chat_rooms", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.Message", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("ChatRoomId") - .HasColumnType("uuid") - .HasColumnName("chat_room_id"); - - b.Property("Content") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .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("EditedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("edited_at"); - - b.Property("ForwardedMessageId") - .HasColumnType("uuid") - .HasColumnName("forwarded_message_id"); - - b.Property>("MembersMentioned") - .HasColumnType("jsonb") - .HasColumnName("members_mentioned"); - - b.Property>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("Nonce") - .IsRequired() - .HasMaxLength(36) - .HasColumnType("character varying(36)") - .HasColumnName("nonce"); - - b.Property("RepliedMessageId") - .HasColumnType("uuid") - .HasColumnName("replied_message_id"); - - b.Property("SenderId") - .HasColumnType("uuid") - .HasColumnName("sender_id"); - - 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_chat_messages"); - - b.HasIndex("ChatRoomId") - .HasDatabaseName("ix_chat_messages_chat_room_id"); - - b.HasIndex("ForwardedMessageId") - .HasDatabaseName("ix_chat_messages_forwarded_message_id"); - - b.HasIndex("RepliedMessageId") - .HasDatabaseName("ix_chat_messages_replied_message_id"); - - b.HasIndex("SenderId") - .HasDatabaseName("ix_chat_messages_sender_id"); - - b.ToTable("chat_messages", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.MessageReaction", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("Attitude") - .HasColumnType("integer") - .HasColumnName("attitude"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("MessageId") - .HasColumnType("uuid") - .HasColumnName("message_id"); - - b.Property("SenderId") - .HasColumnType("uuid") - .HasColumnName("sender_id"); - - b.Property("Symbol") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("symbol"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_chat_reactions"); - - b.HasIndex("MessageId") - .HasDatabaseName("ix_chat_reactions_message_id"); - - b.HasIndex("SenderId") - .HasDatabaseName("ix_chat_reactions_sender_id"); - - b.ToTable("chat_reactions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.RealtimeCall", 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("EndedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("ended_at"); - - b.Property("ProviderName") - .HasColumnType("text") - .HasColumnName("provider_name"); - - b.Property("RoomId") - .HasColumnType("uuid") - .HasColumnName("room_id"); - - b.Property("SenderId") - .HasColumnType("uuid") - .HasColumnName("sender_id"); - - b.Property("SessionId") - .HasColumnType("text") - .HasColumnName("session_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("UpstreamConfigJson") - .HasColumnType("jsonb") - .HasColumnName("upstream"); - - b.HasKey("Id") - .HasName("pk_chat_realtime_call"); - - b.HasIndex("RoomId") - .HasDatabaseName("ix_chat_realtime_call_room_id"); - - b.HasIndex("SenderId") - .HasDatabaseName("ix_chat_realtime_call_sender_id"); - - b.ToTable("chat_realtime_call", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomApp", 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("Name") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("slug"); - - b.Property("Status") - .HasColumnType("integer") - .HasColumnName("status"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("VerifiedAs") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("verified_as"); - - b.Property("VerifiedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("verified_at"); - - b.HasKey("Id") - .HasName("pk_custom_apps"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_custom_apps_publisher_id"); - - b.ToTable("custom_apps", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomAppSecret", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AppId") - .HasColumnType("uuid") - .HasColumnName("app_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("Remarks") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("remarks"); - - b.Property("Secret") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("secret"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_custom_app_secrets"); - - b.HasIndex("AppId") - .HasDatabaseName("ix_custom_app_secrets_app_id"); - - b.ToTable("custom_app_secrets", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroup", 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.Sphere.Permission.PermissionGroupMember", 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.Sphere.Permission.PermissionNode", 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("Area") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("area"); - - 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("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", "Area", "Actor") - .HasDatabaseName("ix_permission_nodes_key_area_actor"); - - b.ToTable("permission_nodes", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.Post", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("Content") - .HasColumnType("text") - .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("Description") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("Downvotes") - .HasColumnType("integer") - .HasColumnName("downvotes"); - - b.Property("EditedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("edited_at"); - - b.Property("ForwardedPostId") - .HasColumnType("uuid") - .HasColumnName("forwarded_post_id"); - - b.Property("Language") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("language"); - - b.Property>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("PublishedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("published_at"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("RepliedPostId") - .HasColumnType("uuid") - .HasColumnName("replied_post_id"); - - b.Property("SearchVector") - .IsRequired() - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("tsvector") - .HasColumnName("search_vector") - .HasAnnotation("Npgsql:TsVectorConfig", "simple") - .HasAnnotation("Npgsql:TsVectorProperties", new[] { "Title", "Description", "Content" }); - - b.Property("ThreadedPostId") - .HasColumnType("uuid") - .HasColumnName("threaded_post_id"); - - b.Property("Title") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("title"); - - b.Property("Type") - .HasColumnType("integer") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("Upvotes") - .HasColumnType("integer") - .HasColumnName("upvotes"); - - b.Property("ViewsTotal") - .HasColumnType("integer") - .HasColumnName("views_total"); - - b.Property("ViewsUnique") - .HasColumnType("integer") - .HasColumnName("views_unique"); - - b.Property("Visibility") - .HasColumnType("integer") - .HasColumnName("visibility"); - - b.HasKey("Id") - .HasName("pk_posts"); - - b.HasIndex("ForwardedPostId") - .HasDatabaseName("ix_posts_forwarded_post_id"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_posts_publisher_id"); - - b.HasIndex("RepliedPostId") - .HasDatabaseName("ix_posts_replied_post_id"); - - b.HasIndex("SearchVector") - .HasDatabaseName("ix_posts_search_vector"); - - NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex("SearchVector"), "GIN"); - - b.HasIndex("ThreadedPostId") - .IsUnique() - .HasDatabaseName("ix_posts_threaded_post_id"); - - b.ToTable("posts", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostCategory", 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("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("name"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_categories"); - - b.ToTable("post_categories", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostCollection", 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("Description") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("name"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_collections"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_post_collections_publisher_id"); - - b.ToTable("post_collections", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostReaction", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("Attitude") - .HasColumnType("integer") - .HasColumnName("attitude"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("PostId") - .HasColumnType("uuid") - .HasColumnName("post_id"); - - b.Property("Symbol") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("symbol"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_reactions"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_post_reactions_account_id"); - - b.HasIndex("PostId") - .HasDatabaseName("ix_post_reactions_post_id"); - - b.ToTable("post_reactions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostTag", 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("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("name"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_tags"); - - b.ToTable("post_tags", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.Publisher", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_id"); - - b.Property("Bio") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("bio"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - 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("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - b.Property("RealmId") - .HasColumnType("uuid") - .HasColumnName("realm_id"); - - b.Property("Type") - .HasColumnType("integer") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_publishers"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_publishers_account_id"); - - b.HasIndex("BackgroundId") - .HasDatabaseName("ix_publishers_background_id"); - - b.HasIndex("Name") - .IsUnique() - .HasDatabaseName("ix_publishers_name"); - - b.HasIndex("PictureId") - .HasDatabaseName("ix_publishers_picture_id"); - - b.HasIndex("RealmId") - .HasDatabaseName("ix_publishers_realm_id"); - - b.ToTable("publishers", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherFeature", 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("ExpiredAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expired_at"); - - b.Property("Flag") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("flag"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_publisher_features"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_publisher_features_publisher_id"); - - b.ToTable("publisher_features", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherMember", b => - { - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_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("Role") - .HasColumnType("integer") - .HasColumnName("role"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("PublisherId", "AccountId") - .HasName("pk_publisher_members"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_publisher_members_account_id"); - - b.ToTable("publisher_members", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherSubscription", 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("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("Status") - .HasColumnType("integer") - .HasColumnName("status"); - - b.Property("Tier") - .HasColumnType("integer") - .HasColumnName("tier"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_publisher_subscriptions"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_publisher_subscriptions_account_id"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_publisher_subscriptions_publisher_id"); - - b.ToTable("publisher_subscriptions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.Realm", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_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("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("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - 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("VerifiedAs") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("verified_as"); - - b.Property("VerifiedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("verified_at"); - - b.HasKey("Id") - .HasName("pk_realms"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_realms_account_id"); - - b.HasIndex("BackgroundId") - .HasDatabaseName("ix_realms_background_id"); - - b.HasIndex("PictureId") - .HasDatabaseName("ix_realms_picture_id"); - - b.HasIndex("Slug") - .IsUnique() - .HasDatabaseName("ix_realms_slug"); - - b.ToTable("realms", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.RealmMember", 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.HasIndex("AccountId") - .HasDatabaseName("ix_realm_members_account_id"); - - b.ToTable("realm_members", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.Sticker", 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("ImageId") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("image_id"); - - b.Property("PackId") - .HasColumnType("uuid") - .HasColumnName("pack_id"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_stickers"); - - b.HasIndex("ImageId") - .HasDatabaseName("ix_stickers_image_id"); - - b.HasIndex("PackId") - .HasDatabaseName("ix_stickers_pack_id"); - - b.HasIndex("Slug") - .HasDatabaseName("ix_stickers_slug"); - - b.ToTable("stickers", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.StickerPack", 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("Description") - .IsRequired() - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("Prefix") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("prefix"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_sticker_packs"); - - b.HasIndex("Prefix") - .IsUnique() - .HasDatabaseName("ix_sticker_packs_prefix"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_sticker_packs_publisher_id"); - - b.ToTable("sticker_packs", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFile", b => - { - b.Property("Id") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .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("Description") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("ExpiredAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expired_at"); - - b.Property>("FileMeta") - .HasColumnType("jsonb") - .HasColumnName("file_meta"); - - b.Property("HasCompression") - .HasColumnType("boolean") - .HasColumnName("has_compression"); - - b.Property("Hash") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("hash"); - - b.Property("MessageId") - .HasColumnType("uuid") - .HasColumnName("message_id"); - - b.Property("MimeType") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("mime_type"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("PostId") - .HasColumnType("uuid") - .HasColumnName("post_id"); - - b.Property>("SensitiveMarks") - .HasColumnType("jsonb") - .HasColumnName("sensitive_marks"); - - b.Property("Size") - .HasColumnType("bigint") - .HasColumnName("size"); - - b.Property("StorageId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("storage_id"); - - b.Property("StorageUrl") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("storage_url"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("UploadedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("uploaded_at"); - - b.Property("UploadedTo") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("uploaded_to"); - - b.Property("Usage") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("usage"); - - b.Property("UsedCount") - .HasColumnType("integer") - .HasColumnName("used_count"); - - b.Property>("UserMeta") - .HasColumnType("jsonb") - .HasColumnName("user_meta"); - - b.HasKey("Id") - .HasName("pk_files"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_files_account_id"); - - b.HasIndex("MessageId") - .HasDatabaseName("ix_files_message_id"); - - b.HasIndex("PostId") - .HasDatabaseName("ix_files_post_id"); - - b.ToTable("files", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Order", 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("ExpiredAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expired_at"); - - b.Property("IssuerAppId") - .HasColumnType("uuid") - .HasColumnName("issuer_app_id"); - - b.Property("PayeeWalletId") - .HasColumnType("uuid") - .HasColumnName("payee_wallet_id"); - - 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("IssuerAppId") - .HasDatabaseName("ix_payment_orders_issuer_app_id"); - - 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.Sphere.Wallet.Transaction", 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.Sphere.Wallet.Wallet", 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.Sphere.Wallet.WalletPocket", 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("PostPostCategory", b => - { - b.Property("CategoriesId") - .HasColumnType("uuid") - .HasColumnName("categories_id"); - - b.Property("PostsId") - .HasColumnType("uuid") - .HasColumnName("posts_id"); - - b.HasKey("CategoriesId", "PostsId") - .HasName("pk_post_category_links"); - - b.HasIndex("PostsId") - .HasDatabaseName("ix_post_category_links_posts_id"); - - b.ToTable("post_category_links", (string)null); - }); - - modelBuilder.Entity("PostPostCollection", b => - { - b.Property("CollectionsId") - .HasColumnType("uuid") - .HasColumnName("collections_id"); - - b.Property("PostsId") - .HasColumnType("uuid") - .HasColumnName("posts_id"); - - b.HasKey("CollectionsId", "PostsId") - .HasName("pk_post_collection_links"); - - b.HasIndex("PostsId") - .HasDatabaseName("ix_post_collection_links_posts_id"); - - b.ToTable("post_collection_links", (string)null); - }); - - modelBuilder.Entity("PostPostTag", b => - { - b.Property("PostsId") - .HasColumnType("uuid") - .HasColumnName("posts_id"); - - b.Property("TagsId") - .HasColumnType("uuid") - .HasColumnName("tags_id"); - - b.HasKey("PostsId", "TagsId") - .HasName("pk_post_tag_links"); - - b.HasIndex("TagsId") - .HasDatabaseName("ix_post_tag_links_tags_id"); - - b.ToTable("post_tag_links", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.AccountAuthFactor", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("AuthFactors") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_auth_factors_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.AccountContact", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Contacts") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_contacts_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.ActionLog", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_action_logs_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Auth.Session", "Session") - .WithMany() - .HasForeignKey("SessionId") - .HasConstraintName("fk_action_logs_auth_sessions_session_id"); - - b.Navigation("Account"); - - b.Navigation("Session"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Badge", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Badges") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_badges_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.CheckInResult", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_check_in_results_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.MagicSpell", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .HasConstraintName("fk_magic_spells_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Notification", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_notifications_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.NotificationPushSubscription", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_notification_push_subscriptions_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Profile", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithOne("Profile") - .HasForeignKey("DysonNetwork.Sphere.Account.Profile", "AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_profiles_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "Background") - .WithMany() - .HasForeignKey("BackgroundId") - .HasConstraintName("fk_account_profiles_files_background_id"); - - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "Picture") - .WithMany() - .HasForeignKey("PictureId") - .HasConstraintName("fk_account_profiles_files_picture_id"); - - b.Navigation("Account"); - - b.Navigation("Background"); - - b.Navigation("Picture"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Relationship", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("OutgoingRelationships") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_relationships_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Account.Account", "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.Sphere.Account.Status", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_statuses_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Activity.Activity", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_activities_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Challenge", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Challenges") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_challenges_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Session", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Sessions") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_sessions_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Auth.Challenge", "Challenge") - .WithMany() - .HasForeignKey("ChallengeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_sessions_auth_challenges_challenge_id"); - - b.Navigation("Account"); - - b.Navigation("Challenge"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatMember", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_members_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatRoom", "ChatRoom") - .WithMany("Members") - .HasForeignKey("ChatRoomId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_members_chat_rooms_chat_room_id"); - - b.Navigation("Account"); - - b.Navigation("ChatRoom"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatRoom", b => - { - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "Background") - .WithMany() - .HasForeignKey("BackgroundId") - .HasConstraintName("fk_chat_rooms_files_background_id"); - - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "Picture") - .WithMany() - .HasForeignKey("PictureId") - .HasConstraintName("fk_chat_rooms_files_picture_id"); - - b.HasOne("DysonNetwork.Sphere.Realm.Realm", "Realm") - .WithMany("ChatRooms") - .HasForeignKey("RealmId") - .HasConstraintName("fk_chat_rooms_realms_realm_id"); - - b.Navigation("Background"); - - b.Navigation("Picture"); - - b.Navigation("Realm"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.Message", b => - { - b.HasOne("DysonNetwork.Sphere.Chat.ChatRoom", "ChatRoom") - .WithMany() - .HasForeignKey("ChatRoomId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_messages_chat_rooms_chat_room_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.Message", "ForwardedMessage") - .WithMany() - .HasForeignKey("ForwardedMessageId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_chat_messages_chat_messages_forwarded_message_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.Message", "RepliedMessage") - .WithMany() - .HasForeignKey("RepliedMessageId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_chat_messages_chat_messages_replied_message_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatMember", "Sender") - .WithMany() - .HasForeignKey("SenderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_messages_chat_members_sender_id"); - - b.Navigation("ChatRoom"); - - b.Navigation("ForwardedMessage"); - - b.Navigation("RepliedMessage"); - - b.Navigation("Sender"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.MessageReaction", b => - { - b.HasOne("DysonNetwork.Sphere.Chat.Message", "Message") - .WithMany("Reactions") - .HasForeignKey("MessageId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_reactions_chat_messages_message_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatMember", "Sender") - .WithMany() - .HasForeignKey("SenderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_reactions_chat_members_sender_id"); - - b.Navigation("Message"); - - b.Navigation("Sender"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.RealtimeCall", b => - { - b.HasOne("DysonNetwork.Sphere.Chat.ChatRoom", "Room") - .WithMany() - .HasForeignKey("RoomId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_realtime_call_chat_rooms_room_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatMember", "Sender") - .WithMany() - .HasForeignKey("SenderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_realtime_call_chat_members_sender_id"); - - b.Navigation("Room"); - - b.Navigation("Sender"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomApp", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Developer") - .WithMany() - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_custom_apps_publishers_publisher_id"); - - b.Navigation("Developer"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomAppSecret", b => - { - b.HasOne("DysonNetwork.Sphere.Developer.CustomApp", "App") - .WithMany() - .HasForeignKey("AppId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_custom_app_secrets_custom_apps_app_id"); - - b.Navigation("App"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroupMember", b => - { - b.HasOne("DysonNetwork.Sphere.Permission.PermissionGroup", "Group") - .WithMany("Members") - .HasForeignKey("GroupId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_permission_group_members_permission_groups_group_id"); - - b.Navigation("Group"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionNode", b => - { - b.HasOne("DysonNetwork.Sphere.Permission.PermissionGroup", "Group") - .WithMany("Nodes") - .HasForeignKey("GroupId") - .HasConstraintName("fk_permission_nodes_permission_groups_group_id"); - - b.Navigation("Group"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.Post", b => - { - b.HasOne("DysonNetwork.Sphere.Post.Post", "ForwardedPost") - .WithMany() - .HasForeignKey("ForwardedPostId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_posts_posts_forwarded_post_id"); - - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Posts") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_posts_publishers_publisher_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", "RepliedPost") - .WithMany() - .HasForeignKey("RepliedPostId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_posts_posts_replied_post_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", "ThreadedPost") - .WithOne() - .HasForeignKey("DysonNetwork.Sphere.Post.Post", "ThreadedPostId") - .HasConstraintName("fk_posts_posts_threaded_post_id"); - - b.Navigation("ForwardedPost"); - - b.Navigation("Publisher"); - - b.Navigation("RepliedPost"); - - b.Navigation("ThreadedPost"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostCollection", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Collections") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_collections_publishers_publisher_id"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostReaction", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_reactions_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", "Post") - .WithMany("Reactions") - .HasForeignKey("PostId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_reactions_posts_post_id"); - - b.Navigation("Account"); - - b.Navigation("Post"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.Publisher", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .HasConstraintName("fk_publishers_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "Background") - .WithMany() - .HasForeignKey("BackgroundId") - .HasConstraintName("fk_publishers_files_background_id"); - - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "Picture") - .WithMany() - .HasForeignKey("PictureId") - .HasConstraintName("fk_publishers_files_picture_id"); - - b.HasOne("DysonNetwork.Sphere.Realm.Realm", "Realm") - .WithMany() - .HasForeignKey("RealmId") - .HasConstraintName("fk_publishers_realms_realm_id"); - - b.Navigation("Account"); - - b.Navigation("Background"); - - b.Navigation("Picture"); - - b.Navigation("Realm"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherFeature", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany() - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_features_publishers_publisher_id"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherMember", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_members_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Members") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_members_publishers_publisher_id"); - - b.Navigation("Account"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherSubscription", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_subscriptions_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Subscriptions") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_subscriptions_publishers_publisher_id"); - - b.Navigation("Account"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.Realm", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_realms_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "Background") - .WithMany() - .HasForeignKey("BackgroundId") - .HasConstraintName("fk_realms_files_background_id"); - - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "Picture") - .WithMany() - .HasForeignKey("PictureId") - .HasConstraintName("fk_realms_files_picture_id"); - - b.Navigation("Account"); - - b.Navigation("Background"); - - b.Navigation("Picture"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.RealmMember", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_realm_members_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Realm.Realm", "Realm") - .WithMany("Members") - .HasForeignKey("RealmId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_realm_members_realms_realm_id"); - - b.Navigation("Account"); - - b.Navigation("Realm"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.Sticker", b => - { - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "Image") - .WithMany() - .HasForeignKey("ImageId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_stickers_files_image_id"); - - b.HasOne("DysonNetwork.Sphere.Sticker.StickerPack", "Pack") - .WithMany() - .HasForeignKey("PackId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_stickers_sticker_packs_pack_id"); - - b.Navigation("Image"); - - b.Navigation("Pack"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.StickerPack", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany() - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_sticker_packs_publishers_publisher_id"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFile", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_files_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.Message", null) - .WithMany("Attachments") - .HasForeignKey("MessageId") - .HasConstraintName("fk_files_chat_messages_message_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany("Attachments") - .HasForeignKey("PostId") - .HasConstraintName("fk_files_posts_post_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Order", b => - { - b.HasOne("DysonNetwork.Sphere.Developer.CustomApp", "IssuerApp") - .WithMany() - .HasForeignKey("IssuerAppId") - .HasConstraintName("fk_payment_orders_custom_apps_issuer_app_id"); - - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "PayeeWallet") - .WithMany() - .HasForeignKey("PayeeWalletId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_payment_orders_wallets_payee_wallet_id"); - - b.HasOne("DysonNetwork.Sphere.Wallet.Transaction", "Transaction") - .WithMany() - .HasForeignKey("TransactionId") - .HasConstraintName("fk_payment_orders_payment_transactions_transaction_id"); - - b.Navigation("IssuerApp"); - - b.Navigation("PayeeWallet"); - - b.Navigation("Transaction"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Transaction", b => - { - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "PayeeWallet") - .WithMany() - .HasForeignKey("PayeeWalletId") - .HasConstraintName("fk_payment_transactions_wallets_payee_wallet_id"); - - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "PayerWallet") - .WithMany() - .HasForeignKey("PayerWalletId") - .HasConstraintName("fk_payment_transactions_wallets_payer_wallet_id"); - - b.Navigation("PayeeWallet"); - - b.Navigation("PayerWallet"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Wallet", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_wallets_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.WalletPocket", b => - { - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "Wallet") - .WithMany("Pockets") - .HasForeignKey("WalletId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_wallet_pockets_wallets_wallet_id"); - - b.Navigation("Wallet"); - }); - - modelBuilder.Entity("PostPostCategory", b => - { - b.HasOne("DysonNetwork.Sphere.Post.PostCategory", null) - .WithMany() - .HasForeignKey("CategoriesId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_category_links_post_categories_categories_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany() - .HasForeignKey("PostsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_category_links_posts_posts_id"); - }); - - modelBuilder.Entity("PostPostCollection", b => - { - b.HasOne("DysonNetwork.Sphere.Post.PostCollection", null) - .WithMany() - .HasForeignKey("CollectionsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_collection_links_post_collections_collections_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany() - .HasForeignKey("PostsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_collection_links_posts_posts_id"); - }); - - modelBuilder.Entity("PostPostTag", b => - { - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany() - .HasForeignKey("PostsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_tag_links_posts_posts_id"); - - b.HasOne("DysonNetwork.Sphere.Post.PostTag", null) - .WithMany() - .HasForeignKey("TagsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_tag_links_post_tags_tags_id"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Account", b => - { - b.Navigation("AuthFactors"); - - b.Navigation("Badges"); - - b.Navigation("Challenges"); - - b.Navigation("Contacts"); - - b.Navigation("IncomingRelationships"); - - b.Navigation("OutgoingRelationships"); - - b.Navigation("Profile") - .IsRequired(); - - b.Navigation("Sessions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatRoom", b => - { - b.Navigation("Members"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.Message", b => - { - b.Navigation("Attachments"); - - b.Navigation("Reactions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroup", b => - { - b.Navigation("Members"); - - b.Navigation("Nodes"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.Post", b => - { - b.Navigation("Attachments"); - - b.Navigation("Reactions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.Publisher", b => - { - b.Navigation("Collections"); - - b.Navigation("Members"); - - b.Navigation("Posts"); - - b.Navigation("Subscriptions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.Realm", b => - { - b.Navigation("ChatRooms"); - - b.Navigation("Members"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Wallet", b => - { - b.Navigation("Pockets"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/DysonNetwork.Sphere/Migrations/20250528171935_AddCloudFileUsage.cs b/DysonNetwork.Sphere/Migrations/20250528171935_AddCloudFileUsage.cs deleted file mode 100644 index 6c57900..0000000 --- a/DysonNetwork.Sphere/Migrations/20250528171935_AddCloudFileUsage.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace DysonNetwork.Sphere.Migrations -{ - /// - public partial class AddCloudFileUsage : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "usage", - table: "files", - type: "character varying(1024)", - maxLength: 1024, - nullable: true); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "usage", - table: "files"); - } - } -} diff --git a/DysonNetwork.Sphere/Migrations/20250601142048_RefactorCloudFileReference.Designer.cs b/DysonNetwork.Sphere/Migrations/20250601142048_RefactorCloudFileReference.Designer.cs deleted file mode 100644 index 156a99d..0000000 --- a/DysonNetwork.Sphere/Migrations/20250601142048_RefactorCloudFileReference.Designer.cs +++ /dev/null @@ -1,3394 +0,0 @@ -// -using System; -using System.Collections.Generic; -using System.Text.Json; -using DysonNetwork.Sphere; -using DysonNetwork.Sphere.Account; -using DysonNetwork.Sphere.Storage; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using NetTopologySuite.Geometries; -using NodaTime; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; -using NpgsqlTypes; - -#nullable disable - -namespace DysonNetwork.Sphere.Migrations -{ - [DbContext(typeof(AppDatabase))] - [Migration("20250601142048_RefactorCloudFileReference")] - partial class RefactorCloudFileReference - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "9.0.3") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "postgis"); - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Account", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("ActivatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("activated_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("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("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.Sphere.Account.AccountAuthFactor", 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("Secret") - .HasMaxLength(8196) - .HasColumnType("character varying(8196)") - .HasColumnName("secret"); - - 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.Sphere.Account.AccountContact", 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("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.Sphere.Account.ActionLog", 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("geometry") - .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.HasIndex("SessionId") - .HasDatabaseName("ix_action_logs_session_id"); - - b.ToTable("action_logs", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Badge", 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("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.Sphere.Account.CheckInResult", 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("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.Sphere.Account.MagicSpell", 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.Sphere.Account.Notification", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("Content") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .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>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("Priority") - .HasColumnType("integer") - .HasColumnName("priority"); - - b.Property("Subtitle") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("subtitle"); - - b.Property("Title") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("title"); - - b.Property("Topic") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("topic"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("ViewedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("viewed_at"); - - b.HasKey("Id") - .HasName("pk_notifications"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_notifications_account_id"); - - b.ToTable("notifications", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.NotificationPushSubscription", 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(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("device_id"); - - b.Property("DeviceToken") - .IsRequired() - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("device_token"); - - b.Property("LastUsedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_used_at"); - - b.Property("Provider") - .HasColumnType("integer") - .HasColumnName("provider"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_notification_push_subscriptions"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_notification_push_subscriptions_account_id"); - - b.HasIndex("DeviceToken", "DeviceId") - .IsUnique() - .HasDatabaseName("ix_notification_push_subscriptions_device_token_device_id"); - - b.ToTable("notification_push_subscriptions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Profile", 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("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_id"); - - 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("MiddleName") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("middle_name"); - - b.Property("Picture") - .HasColumnType("jsonb") - .HasColumnName("picture"); - - b.Property("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - b.Property("Pronouns") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("pronouns"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - 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.Sphere.Account.Relationship", 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.Sphere.Account.Status", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - 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("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("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.Sphere.Activity.Activity", 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>("Meta") - .IsRequired() - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("ResourceIdentifier") - .IsRequired() - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("resource_identifier"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property>("UsersVisible") - .IsRequired() - .HasColumnType("jsonb") - .HasColumnName("users_visible"); - - b.Property("Visibility") - .HasColumnType("integer") - .HasColumnName("visibility"); - - b.HasKey("Id") - .HasName("pk_activities"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_activities_account_id"); - - b.ToTable("activities", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Challenge", 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") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("device_id"); - - 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("geometry") - .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("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_challenges"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_auth_challenges_account_id"); - - b.ToTable("auth_challenges", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Session", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("ChallengeId") - .HasColumnType("uuid") - .HasColumnName("challenge_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("Label") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("label"); - - b.Property("LastGrantedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_granted_at"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - 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.ToTable("auth_sessions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatMember", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("ChatRoomId") - .HasColumnType("uuid") - .HasColumnName("chat_room_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("IsBot") - .HasColumnType("boolean") - .HasColumnName("is_bot"); - - b.Property("JoinedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("joined_at"); - - b.Property("LastReadAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_read_at"); - - b.Property("LeaveAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("leave_at"); - - b.Property("Nick") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("nick"); - - b.Property("Notify") - .HasColumnType("integer") - .HasColumnName("notify"); - - b.Property("Role") - .HasColumnType("integer") - .HasColumnName("role"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_chat_members"); - - b.HasAlternateKey("ChatRoomId", "AccountId") - .HasName("ak_chat_members_chat_room_id_account_id"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_chat_members_account_id"); - - b.ToTable("chat_members", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatRoom", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("Background") - .HasColumnType("jsonb") - .HasColumnName("background"); - - b.Property("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_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("Description") - .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") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("Picture") - .HasColumnType("jsonb") - .HasColumnName("picture"); - - b.Property("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - b.Property("RealmId") - .HasColumnType("uuid") - .HasColumnName("realm_id"); - - b.Property("Type") - .HasColumnType("integer") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_chat_rooms"); - - b.HasIndex("RealmId") - .HasDatabaseName("ix_chat_rooms_realm_id"); - - b.ToTable("chat_rooms", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.Message", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property>("Attachments") - .IsRequired() - .HasColumnType("jsonb") - .HasColumnName("attachments"); - - b.Property("ChatRoomId") - .HasColumnType("uuid") - .HasColumnName("chat_room_id"); - - b.Property("Content") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .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("EditedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("edited_at"); - - b.Property("ForwardedMessageId") - .HasColumnType("uuid") - .HasColumnName("forwarded_message_id"); - - b.Property>("MembersMentioned") - .HasColumnType("jsonb") - .HasColumnName("members_mentioned"); - - b.Property>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("Nonce") - .IsRequired() - .HasMaxLength(36) - .HasColumnType("character varying(36)") - .HasColumnName("nonce"); - - b.Property("RepliedMessageId") - .HasColumnType("uuid") - .HasColumnName("replied_message_id"); - - b.Property("SenderId") - .HasColumnType("uuid") - .HasColumnName("sender_id"); - - 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_chat_messages"); - - b.HasIndex("ChatRoomId") - .HasDatabaseName("ix_chat_messages_chat_room_id"); - - b.HasIndex("ForwardedMessageId") - .HasDatabaseName("ix_chat_messages_forwarded_message_id"); - - b.HasIndex("RepliedMessageId") - .HasDatabaseName("ix_chat_messages_replied_message_id"); - - b.HasIndex("SenderId") - .HasDatabaseName("ix_chat_messages_sender_id"); - - b.ToTable("chat_messages", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.MessageReaction", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("Attitude") - .HasColumnType("integer") - .HasColumnName("attitude"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("MessageId") - .HasColumnType("uuid") - .HasColumnName("message_id"); - - b.Property("SenderId") - .HasColumnType("uuid") - .HasColumnName("sender_id"); - - b.Property("Symbol") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("symbol"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_chat_reactions"); - - b.HasIndex("MessageId") - .HasDatabaseName("ix_chat_reactions_message_id"); - - b.HasIndex("SenderId") - .HasDatabaseName("ix_chat_reactions_sender_id"); - - b.ToTable("chat_reactions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.RealtimeCall", 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("EndedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("ended_at"); - - b.Property("ProviderName") - .HasColumnType("text") - .HasColumnName("provider_name"); - - b.Property("RoomId") - .HasColumnType("uuid") - .HasColumnName("room_id"); - - b.Property("SenderId") - .HasColumnType("uuid") - .HasColumnName("sender_id"); - - b.Property("SessionId") - .HasColumnType("text") - .HasColumnName("session_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("UpstreamConfigJson") - .HasColumnType("jsonb") - .HasColumnName("upstream"); - - b.HasKey("Id") - .HasName("pk_chat_realtime_call"); - - b.HasIndex("RoomId") - .HasDatabaseName("ix_chat_realtime_call_room_id"); - - b.HasIndex("SenderId") - .HasDatabaseName("ix_chat_realtime_call_sender_id"); - - b.ToTable("chat_realtime_call", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomApp", 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("Name") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("slug"); - - b.Property("Status") - .HasColumnType("integer") - .HasColumnName("status"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("VerifiedAs") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("verified_as"); - - b.Property("VerifiedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("verified_at"); - - b.HasKey("Id") - .HasName("pk_custom_apps"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_custom_apps_publisher_id"); - - b.ToTable("custom_apps", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomAppSecret", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AppId") - .HasColumnType("uuid") - .HasColumnName("app_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("Remarks") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("remarks"); - - b.Property("Secret") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("secret"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_custom_app_secrets"); - - b.HasIndex("AppId") - .HasDatabaseName("ix_custom_app_secrets_app_id"); - - b.ToTable("custom_app_secrets", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroup", 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.Sphere.Permission.PermissionGroupMember", 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.Sphere.Permission.PermissionNode", 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("Area") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("area"); - - 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("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", "Area", "Actor") - .HasDatabaseName("ix_permission_nodes_key_area_actor"); - - b.ToTable("permission_nodes", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.Post", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property>("Attachments") - .IsRequired() - .HasColumnType("jsonb") - .HasColumnName("attachments"); - - b.Property("Content") - .HasColumnType("text") - .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("Description") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("Downvotes") - .HasColumnType("integer") - .HasColumnName("downvotes"); - - b.Property("EditedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("edited_at"); - - b.Property("ForwardedPostId") - .HasColumnType("uuid") - .HasColumnName("forwarded_post_id"); - - b.Property("Language") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("language"); - - b.Property>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("PublishedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("published_at"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("RepliedPostId") - .HasColumnType("uuid") - .HasColumnName("replied_post_id"); - - b.Property("SearchVector") - .IsRequired() - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("tsvector") - .HasColumnName("search_vector") - .HasAnnotation("Npgsql:TsVectorConfig", "simple") - .HasAnnotation("Npgsql:TsVectorProperties", new[] { "Title", "Description", "Content" }); - - b.Property("Title") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("title"); - - b.Property("Type") - .HasColumnType("integer") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("Upvotes") - .HasColumnType("integer") - .HasColumnName("upvotes"); - - b.Property("ViewsTotal") - .HasColumnType("integer") - .HasColumnName("views_total"); - - b.Property("ViewsUnique") - .HasColumnType("integer") - .HasColumnName("views_unique"); - - b.Property("Visibility") - .HasColumnType("integer") - .HasColumnName("visibility"); - - b.HasKey("Id") - .HasName("pk_posts"); - - b.HasIndex("ForwardedPostId") - .HasDatabaseName("ix_posts_forwarded_post_id"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_posts_publisher_id"); - - b.HasIndex("RepliedPostId") - .HasDatabaseName("ix_posts_replied_post_id"); - - b.HasIndex("SearchVector") - .HasDatabaseName("ix_posts_search_vector"); - - NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex("SearchVector"), "GIN"); - - b.ToTable("posts", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostCategory", 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("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("name"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_categories"); - - b.ToTable("post_categories", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostCollection", 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("Description") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("name"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_collections"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_post_collections_publisher_id"); - - b.ToTable("post_collections", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostReaction", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("Attitude") - .HasColumnType("integer") - .HasColumnName("attitude"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("PostId") - .HasColumnType("uuid") - .HasColumnName("post_id"); - - b.Property("Symbol") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("symbol"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_reactions"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_post_reactions_account_id"); - - b.HasIndex("PostId") - .HasDatabaseName("ix_post_reactions_post_id"); - - b.ToTable("post_reactions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostTag", 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("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("name"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_tags"); - - b.ToTable("post_tags", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.Publisher", 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("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_id"); - - b.Property("Bio") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("bio"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - 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("Picture") - .HasColumnType("jsonb") - .HasColumnName("picture"); - - b.Property("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - b.Property("RealmId") - .HasColumnType("uuid") - .HasColumnName("realm_id"); - - b.Property("Type") - .HasColumnType("integer") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_publishers"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_publishers_account_id"); - - b.HasIndex("Name") - .IsUnique() - .HasDatabaseName("ix_publishers_name"); - - b.HasIndex("RealmId") - .HasDatabaseName("ix_publishers_realm_id"); - - b.ToTable("publishers", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherFeature", 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("ExpiredAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expired_at"); - - b.Property("Flag") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("flag"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_publisher_features"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_publisher_features_publisher_id"); - - b.ToTable("publisher_features", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherMember", b => - { - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_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("Role") - .HasColumnType("integer") - .HasColumnName("role"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("PublisherId", "AccountId") - .HasName("pk_publisher_members"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_publisher_members_account_id"); - - b.ToTable("publisher_members", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherSubscription", 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("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("Status") - .HasColumnType("integer") - .HasColumnName("status"); - - b.Property("Tier") - .HasColumnType("integer") - .HasColumnName("tier"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_publisher_subscriptions"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_publisher_subscriptions_account_id"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_publisher_subscriptions_publisher_id"); - - b.ToTable("publisher_subscriptions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.Realm", 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("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_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("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("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - 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("VerifiedAs") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("verified_as"); - - b.Property("VerifiedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("verified_at"); - - b.HasKey("Id") - .HasName("pk_realms"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_realms_account_id"); - - b.HasIndex("Slug") - .IsUnique() - .HasDatabaseName("ix_realms_slug"); - - b.ToTable("realms", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.RealmMember", 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.HasIndex("AccountId") - .HasDatabaseName("ix_realm_members_account_id"); - - b.ToTable("realm_members", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.Sticker", 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("Image") - .HasColumnType("jsonb") - .HasColumnName("image"); - - b.Property("ImageId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("image_id"); - - b.Property("PackId") - .HasColumnType("uuid") - .HasColumnName("pack_id"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_stickers"); - - b.HasIndex("PackId") - .HasDatabaseName("ix_stickers_pack_id"); - - b.HasIndex("Slug") - .HasDatabaseName("ix_stickers_slug"); - - b.ToTable("stickers", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.StickerPack", 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("Description") - .IsRequired() - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("Prefix") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("prefix"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_sticker_packs"); - - b.HasIndex("Prefix") - .IsUnique() - .HasDatabaseName("ix_sticker_packs_prefix"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_sticker_packs_publisher_id"); - - b.ToTable("sticker_packs", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFile", b => - { - b.Property("Id") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .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("Description") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property>("FileMeta") - .HasColumnType("jsonb") - .HasColumnName("file_meta"); - - b.Property("HasCompression") - .HasColumnType("boolean") - .HasColumnName("has_compression"); - - b.Property("Hash") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("hash"); - - b.Property("MessageId") - .HasColumnType("uuid") - .HasColumnName("message_id"); - - b.Property("MimeType") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("mime_type"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("PostId") - .HasColumnType("uuid") - .HasColumnName("post_id"); - - b.Property>("SensitiveMarks") - .HasColumnType("jsonb") - .HasColumnName("sensitive_marks"); - - b.Property("Size") - .HasColumnType("bigint") - .HasColumnName("size"); - - b.Property("StorageId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("storage_id"); - - b.Property("StorageUrl") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("storage_url"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("UploadedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("uploaded_at"); - - b.Property("UploadedTo") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("uploaded_to"); - - b.Property>("UserMeta") - .HasColumnType("jsonb") - .HasColumnName("user_meta"); - - b.HasKey("Id") - .HasName("pk_files"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_files_account_id"); - - b.HasIndex("MessageId") - .HasDatabaseName("ix_files_message_id"); - - b.HasIndex("PostId") - .HasDatabaseName("ix_files_post_id"); - - b.ToTable("files", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFileReference", 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("ExpiredAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expired_at"); - - b.Property("FileId") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("file_id"); - - b.Property("ResourceId") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("resource_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("Usage") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("usage"); - - b.HasKey("Id") - .HasName("pk_file_references"); - - b.HasIndex("FileId") - .HasDatabaseName("ix_file_references_file_id"); - - b.ToTable("file_references", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Order", 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("ExpiredAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expired_at"); - - b.Property("IssuerAppId") - .HasColumnType("uuid") - .HasColumnName("issuer_app_id"); - - b.Property("PayeeWalletId") - .HasColumnType("uuid") - .HasColumnName("payee_wallet_id"); - - 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("IssuerAppId") - .HasDatabaseName("ix_payment_orders_issuer_app_id"); - - 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.Sphere.Wallet.Transaction", 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.Sphere.Wallet.Wallet", 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.Sphere.Wallet.WalletPocket", 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("PostPostCategory", b => - { - b.Property("CategoriesId") - .HasColumnType("uuid") - .HasColumnName("categories_id"); - - b.Property("PostsId") - .HasColumnType("uuid") - .HasColumnName("posts_id"); - - b.HasKey("CategoriesId", "PostsId") - .HasName("pk_post_category_links"); - - b.HasIndex("PostsId") - .HasDatabaseName("ix_post_category_links_posts_id"); - - b.ToTable("post_category_links", (string)null); - }); - - modelBuilder.Entity("PostPostCollection", b => - { - b.Property("CollectionsId") - .HasColumnType("uuid") - .HasColumnName("collections_id"); - - b.Property("PostsId") - .HasColumnType("uuid") - .HasColumnName("posts_id"); - - b.HasKey("CollectionsId", "PostsId") - .HasName("pk_post_collection_links"); - - b.HasIndex("PostsId") - .HasDatabaseName("ix_post_collection_links_posts_id"); - - b.ToTable("post_collection_links", (string)null); - }); - - modelBuilder.Entity("PostPostTag", b => - { - b.Property("PostsId") - .HasColumnType("uuid") - .HasColumnName("posts_id"); - - b.Property("TagsId") - .HasColumnType("uuid") - .HasColumnName("tags_id"); - - b.HasKey("PostsId", "TagsId") - .HasName("pk_post_tag_links"); - - b.HasIndex("TagsId") - .HasDatabaseName("ix_post_tag_links_tags_id"); - - b.ToTable("post_tag_links", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.AccountAuthFactor", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("AuthFactors") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_auth_factors_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.AccountContact", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Contacts") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_contacts_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.ActionLog", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_action_logs_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Auth.Session", "Session") - .WithMany() - .HasForeignKey("SessionId") - .HasConstraintName("fk_action_logs_auth_sessions_session_id"); - - b.Navigation("Account"); - - b.Navigation("Session"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Badge", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Badges") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_badges_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.CheckInResult", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_check_in_results_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.MagicSpell", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .HasConstraintName("fk_magic_spells_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Notification", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_notifications_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.NotificationPushSubscription", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_notification_push_subscriptions_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Profile", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithOne("Profile") - .HasForeignKey("DysonNetwork.Sphere.Account.Profile", "AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_profiles_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Relationship", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("OutgoingRelationships") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_relationships_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Account.Account", "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.Sphere.Account.Status", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_statuses_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Activity.Activity", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_activities_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Challenge", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Challenges") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_challenges_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Session", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Sessions") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_sessions_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Auth.Challenge", "Challenge") - .WithMany() - .HasForeignKey("ChallengeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_sessions_auth_challenges_challenge_id"); - - b.Navigation("Account"); - - b.Navigation("Challenge"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatMember", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_members_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatRoom", "ChatRoom") - .WithMany("Members") - .HasForeignKey("ChatRoomId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_members_chat_rooms_chat_room_id"); - - b.Navigation("Account"); - - b.Navigation("ChatRoom"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatRoom", b => - { - b.HasOne("DysonNetwork.Sphere.Realm.Realm", "Realm") - .WithMany("ChatRooms") - .HasForeignKey("RealmId") - .HasConstraintName("fk_chat_rooms_realms_realm_id"); - - b.Navigation("Realm"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.Message", b => - { - b.HasOne("DysonNetwork.Sphere.Chat.ChatRoom", "ChatRoom") - .WithMany() - .HasForeignKey("ChatRoomId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_messages_chat_rooms_chat_room_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.Message", "ForwardedMessage") - .WithMany() - .HasForeignKey("ForwardedMessageId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_chat_messages_chat_messages_forwarded_message_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.Message", "RepliedMessage") - .WithMany() - .HasForeignKey("RepliedMessageId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_chat_messages_chat_messages_replied_message_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatMember", "Sender") - .WithMany() - .HasForeignKey("SenderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_messages_chat_members_sender_id"); - - b.Navigation("ChatRoom"); - - b.Navigation("ForwardedMessage"); - - b.Navigation("RepliedMessage"); - - b.Navigation("Sender"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.MessageReaction", b => - { - b.HasOne("DysonNetwork.Sphere.Chat.Message", "Message") - .WithMany("Reactions") - .HasForeignKey("MessageId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_reactions_chat_messages_message_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatMember", "Sender") - .WithMany() - .HasForeignKey("SenderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_reactions_chat_members_sender_id"); - - b.Navigation("Message"); - - b.Navigation("Sender"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.RealtimeCall", b => - { - b.HasOne("DysonNetwork.Sphere.Chat.ChatRoom", "Room") - .WithMany() - .HasForeignKey("RoomId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_realtime_call_chat_rooms_room_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatMember", "Sender") - .WithMany() - .HasForeignKey("SenderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_realtime_call_chat_members_sender_id"); - - b.Navigation("Room"); - - b.Navigation("Sender"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomApp", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Developer") - .WithMany() - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_custom_apps_publishers_publisher_id"); - - b.Navigation("Developer"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomAppSecret", b => - { - b.HasOne("DysonNetwork.Sphere.Developer.CustomApp", "App") - .WithMany() - .HasForeignKey("AppId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_custom_app_secrets_custom_apps_app_id"); - - b.Navigation("App"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroupMember", b => - { - b.HasOne("DysonNetwork.Sphere.Permission.PermissionGroup", "Group") - .WithMany("Members") - .HasForeignKey("GroupId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_permission_group_members_permission_groups_group_id"); - - b.Navigation("Group"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionNode", b => - { - b.HasOne("DysonNetwork.Sphere.Permission.PermissionGroup", "Group") - .WithMany("Nodes") - .HasForeignKey("GroupId") - .HasConstraintName("fk_permission_nodes_permission_groups_group_id"); - - b.Navigation("Group"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.Post", b => - { - b.HasOne("DysonNetwork.Sphere.Post.Post", "ForwardedPost") - .WithMany() - .HasForeignKey("ForwardedPostId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_posts_posts_forwarded_post_id"); - - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Posts") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_posts_publishers_publisher_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", "RepliedPost") - .WithMany() - .HasForeignKey("RepliedPostId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_posts_posts_replied_post_id"); - - b.Navigation("ForwardedPost"); - - b.Navigation("Publisher"); - - b.Navigation("RepliedPost"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostCollection", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Collections") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_collections_publishers_publisher_id"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostReaction", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_reactions_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", "Post") - .WithMany("Reactions") - .HasForeignKey("PostId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_reactions_posts_post_id"); - - b.Navigation("Account"); - - b.Navigation("Post"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.Publisher", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .HasConstraintName("fk_publishers_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Realm.Realm", "Realm") - .WithMany() - .HasForeignKey("RealmId") - .HasConstraintName("fk_publishers_realms_realm_id"); - - b.Navigation("Account"); - - b.Navigation("Realm"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherFeature", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany() - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_features_publishers_publisher_id"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherMember", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_members_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Members") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_members_publishers_publisher_id"); - - b.Navigation("Account"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherSubscription", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_subscriptions_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Subscriptions") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_subscriptions_publishers_publisher_id"); - - b.Navigation("Account"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.Realm", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_realms_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.RealmMember", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_realm_members_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Realm.Realm", "Realm") - .WithMany("Members") - .HasForeignKey("RealmId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_realm_members_realms_realm_id"); - - b.Navigation("Account"); - - b.Navigation("Realm"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.Sticker", b => - { - b.HasOne("DysonNetwork.Sphere.Sticker.StickerPack", "Pack") - .WithMany() - .HasForeignKey("PackId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_stickers_sticker_packs_pack_id"); - - b.Navigation("Pack"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.StickerPack", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany() - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_sticker_packs_publishers_publisher_id"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFile", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_files_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.Message", null) - .WithMany("OutdatedAttachments") - .HasForeignKey("MessageId") - .HasConstraintName("fk_files_chat_messages_message_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany("OutdatedAttachments") - .HasForeignKey("PostId") - .HasConstraintName("fk_files_posts_post_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFileReference", b => - { - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "File") - .WithMany() - .HasForeignKey("FileId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_file_references_files_file_id"); - - b.Navigation("File"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Order", b => - { - b.HasOne("DysonNetwork.Sphere.Developer.CustomApp", "IssuerApp") - .WithMany() - .HasForeignKey("IssuerAppId") - .HasConstraintName("fk_payment_orders_custom_apps_issuer_app_id"); - - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "PayeeWallet") - .WithMany() - .HasForeignKey("PayeeWalletId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_payment_orders_wallets_payee_wallet_id"); - - b.HasOne("DysonNetwork.Sphere.Wallet.Transaction", "Transaction") - .WithMany() - .HasForeignKey("TransactionId") - .HasConstraintName("fk_payment_orders_payment_transactions_transaction_id"); - - b.Navigation("IssuerApp"); - - b.Navigation("PayeeWallet"); - - b.Navigation("Transaction"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Transaction", b => - { - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "PayeeWallet") - .WithMany() - .HasForeignKey("PayeeWalletId") - .HasConstraintName("fk_payment_transactions_wallets_payee_wallet_id"); - - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "PayerWallet") - .WithMany() - .HasForeignKey("PayerWalletId") - .HasConstraintName("fk_payment_transactions_wallets_payer_wallet_id"); - - b.Navigation("PayeeWallet"); - - b.Navigation("PayerWallet"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Wallet", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_wallets_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.WalletPocket", b => - { - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "Wallet") - .WithMany("Pockets") - .HasForeignKey("WalletId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_wallet_pockets_wallets_wallet_id"); - - b.Navigation("Wallet"); - }); - - modelBuilder.Entity("PostPostCategory", b => - { - b.HasOne("DysonNetwork.Sphere.Post.PostCategory", null) - .WithMany() - .HasForeignKey("CategoriesId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_category_links_post_categories_categories_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany() - .HasForeignKey("PostsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_category_links_posts_posts_id"); - }); - - modelBuilder.Entity("PostPostCollection", b => - { - b.HasOne("DysonNetwork.Sphere.Post.PostCollection", null) - .WithMany() - .HasForeignKey("CollectionsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_collection_links_post_collections_collections_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany() - .HasForeignKey("PostsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_collection_links_posts_posts_id"); - }); - - modelBuilder.Entity("PostPostTag", b => - { - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany() - .HasForeignKey("PostsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_tag_links_posts_posts_id"); - - b.HasOne("DysonNetwork.Sphere.Post.PostTag", null) - .WithMany() - .HasForeignKey("TagsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_tag_links_post_tags_tags_id"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Account", b => - { - b.Navigation("AuthFactors"); - - b.Navigation("Badges"); - - b.Navigation("Challenges"); - - b.Navigation("Contacts"); - - b.Navigation("IncomingRelationships"); - - b.Navigation("OutgoingRelationships"); - - b.Navigation("Profile") - .IsRequired(); - - b.Navigation("Sessions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatRoom", b => - { - b.Navigation("Members"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.Message", b => - { - b.Navigation("OutdatedAttachments"); - - b.Navigation("Reactions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroup", b => - { - b.Navigation("Members"); - - b.Navigation("Nodes"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.Post", b => - { - b.Navigation("OutdatedAttachments"); - - b.Navigation("Reactions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.Publisher", b => - { - b.Navigation("Collections"); - - b.Navigation("Members"); - - b.Navigation("Posts"); - - b.Navigation("Subscriptions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.Realm", b => - { - b.Navigation("ChatRooms"); - - b.Navigation("Members"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Wallet", b => - { - b.Navigation("Pockets"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/DysonNetwork.Sphere/Migrations/20250601142048_RefactorCloudFileReference.cs b/DysonNetwork.Sphere/Migrations/20250601142048_RefactorCloudFileReference.cs deleted file mode 100644 index 1be30f6..0000000 --- a/DysonNetwork.Sphere/Migrations/20250601142048_RefactorCloudFileReference.cs +++ /dev/null @@ -1,436 +0,0 @@ -using System; -using System.Collections.Generic; -using DysonNetwork.Sphere.Storage; -using Microsoft.EntityFrameworkCore.Migrations; -using NodaTime; - -#nullable disable - -namespace DysonNetwork.Sphere.Migrations -{ - /// - public partial class RefactorCloudFileReference : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropForeignKey( - name: "fk_account_profiles_files_background_id", - table: "account_profiles"); - - migrationBuilder.DropForeignKey( - name: "fk_account_profiles_files_picture_id", - table: "account_profiles"); - - migrationBuilder.DropForeignKey( - name: "fk_chat_rooms_files_background_id", - table: "chat_rooms"); - - migrationBuilder.DropForeignKey( - name: "fk_chat_rooms_files_picture_id", - table: "chat_rooms"); - - migrationBuilder.DropForeignKey( - name: "fk_posts_posts_threaded_post_id", - table: "posts"); - - migrationBuilder.DropForeignKey( - name: "fk_publishers_files_background_id", - table: "publishers"); - - migrationBuilder.DropForeignKey( - name: "fk_publishers_files_picture_id", - table: "publishers"); - - migrationBuilder.DropForeignKey( - name: "fk_realms_files_background_id", - table: "realms"); - - migrationBuilder.DropForeignKey( - name: "fk_realms_files_picture_id", - table: "realms"); - - migrationBuilder.DropForeignKey( - name: "fk_stickers_files_image_id", - table: "stickers"); - - migrationBuilder.DropIndex( - name: "ix_stickers_image_id", - table: "stickers"); - - migrationBuilder.DropIndex( - name: "ix_realms_background_id", - table: "realms"); - - migrationBuilder.DropIndex( - name: "ix_realms_picture_id", - table: "realms"); - - migrationBuilder.DropIndex( - name: "ix_publishers_background_id", - table: "publishers"); - - migrationBuilder.DropIndex( - name: "ix_publishers_picture_id", - table: "publishers"); - - migrationBuilder.DropIndex( - name: "ix_posts_threaded_post_id", - table: "posts"); - - migrationBuilder.DropIndex( - name: "ix_chat_rooms_background_id", - table: "chat_rooms"); - - migrationBuilder.DropIndex( - name: "ix_chat_rooms_picture_id", - table: "chat_rooms"); - - migrationBuilder.DropIndex( - name: "ix_account_profiles_background_id", - table: "account_profiles"); - - migrationBuilder.DropIndex( - name: "ix_account_profiles_picture_id", - table: "account_profiles"); - - migrationBuilder.DropColumn( - name: "threaded_post_id", - table: "posts"); - - migrationBuilder.DropColumn( - name: "expired_at", - table: "files"); - - migrationBuilder.DropColumn( - name: "usage", - table: "files"); - - migrationBuilder.DropColumn( - name: "used_count", - table: "files"); - - migrationBuilder.AlterColumn( - name: "image_id", - table: "stickers", - type: "character varying(32)", - maxLength: 32, - nullable: true, - oldClrType: typeof(string), - oldType: "character varying(32)", - oldMaxLength: 32); - - migrationBuilder.AddColumn( - name: "image", - table: "stickers", - type: "jsonb", - nullable: true, - defaultValueSql: "'[]'::jsonb" - ); - - migrationBuilder.AddColumn( - name: "background", - table: "realms", - type: "jsonb", - nullable: true); - - migrationBuilder.AddColumn( - name: "picture", - table: "realms", - type: "jsonb", - nullable: true); - - migrationBuilder.AddColumn( - name: "background", - table: "publishers", - type: "jsonb", - nullable: true); - - migrationBuilder.AddColumn( - name: "picture", - table: "publishers", - type: "jsonb", - nullable: true); - - migrationBuilder.AddColumn>( - name: "attachments", - table: "posts", - type: "jsonb", - nullable: false, - defaultValueSql: "'[]'::jsonb" - ); - - migrationBuilder.AddColumn( - name: "background", - table: "chat_rooms", - type: "jsonb", - nullable: true); - - migrationBuilder.AddColumn( - name: "picture", - table: "chat_rooms", - type: "jsonb", - nullable: true); - - migrationBuilder.AddColumn>( - name: "attachments", - table: "chat_messages", - type: "jsonb", - nullable: false, - defaultValueSql: "'[]'::jsonb" - ); - - migrationBuilder.AddColumn( - name: "background", - table: "account_profiles", - type: "jsonb", - nullable: true); - - migrationBuilder.AddColumn( - name: "picture", - table: "account_profiles", - type: "jsonb", - nullable: true); - - migrationBuilder.CreateTable( - name: "file_references", - columns: table => new - { - id = table.Column(type: "uuid", nullable: false), - file_id = table.Column(type: "character varying(32)", maxLength: 32, nullable: false), - usage = table.Column(type: "character varying(1024)", maxLength: 1024, nullable: false), - resource_id = table.Column(type: "character varying(1024)", maxLength: 1024, nullable: false), - expired_at = table.Column(type: "timestamp with time zone", nullable: true), - created_at = table.Column(type: "timestamp with time zone", nullable: false), - updated_at = table.Column(type: "timestamp with time zone", nullable: false), - deleted_at = table.Column(type: "timestamp with time zone", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("pk_file_references", x => x.id); - table.ForeignKey( - name: "fk_file_references_files_file_id", - column: x => x.file_id, - principalTable: "files", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateIndex( - name: "ix_file_references_file_id", - table: "file_references", - column: "file_id"); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "file_references"); - - migrationBuilder.DropColumn( - name: "image", - table: "stickers"); - - migrationBuilder.DropColumn( - name: "background", - table: "realms"); - - migrationBuilder.DropColumn( - name: "picture", - table: "realms"); - - migrationBuilder.DropColumn( - name: "background", - table: "publishers"); - - migrationBuilder.DropColumn( - name: "picture", - table: "publishers"); - - migrationBuilder.DropColumn( - name: "attachments", - table: "posts"); - - migrationBuilder.DropColumn( - name: "background", - table: "chat_rooms"); - - migrationBuilder.DropColumn( - name: "picture", - table: "chat_rooms"); - - migrationBuilder.DropColumn( - name: "attachments", - table: "chat_messages"); - - migrationBuilder.DropColumn( - name: "background", - table: "account_profiles"); - - migrationBuilder.DropColumn( - name: "picture", - table: "account_profiles"); - - migrationBuilder.AlterColumn( - name: "image_id", - table: "stickers", - type: "character varying(32)", - maxLength: 32, - nullable: false, - defaultValue: "", - oldClrType: typeof(string), - oldType: "character varying(32)", - oldMaxLength: 32, - oldNullable: true); - - migrationBuilder.AddColumn( - name: "threaded_post_id", - table: "posts", - type: "uuid", - nullable: true); - - migrationBuilder.AddColumn( - name: "expired_at", - table: "files", - type: "timestamp with time zone", - nullable: true); - - migrationBuilder.AddColumn( - name: "usage", - table: "files", - type: "character varying(1024)", - maxLength: 1024, - nullable: true); - - migrationBuilder.AddColumn( - name: "used_count", - table: "files", - type: "integer", - nullable: false, - defaultValue: 0); - - migrationBuilder.CreateIndex( - name: "ix_stickers_image_id", - table: "stickers", - column: "image_id"); - - migrationBuilder.CreateIndex( - name: "ix_realms_background_id", - table: "realms", - column: "background_id"); - - migrationBuilder.CreateIndex( - name: "ix_realms_picture_id", - table: "realms", - column: "picture_id"); - - migrationBuilder.CreateIndex( - name: "ix_publishers_background_id", - table: "publishers", - column: "background_id"); - - migrationBuilder.CreateIndex( - name: "ix_publishers_picture_id", - table: "publishers", - column: "picture_id"); - - migrationBuilder.CreateIndex( - name: "ix_posts_threaded_post_id", - table: "posts", - column: "threaded_post_id", - unique: true); - - migrationBuilder.CreateIndex( - name: "ix_chat_rooms_background_id", - table: "chat_rooms", - column: "background_id"); - - migrationBuilder.CreateIndex( - name: "ix_chat_rooms_picture_id", - table: "chat_rooms", - column: "picture_id"); - - migrationBuilder.CreateIndex( - name: "ix_account_profiles_background_id", - table: "account_profiles", - column: "background_id"); - - migrationBuilder.CreateIndex( - name: "ix_account_profiles_picture_id", - table: "account_profiles", - column: "picture_id"); - - migrationBuilder.AddForeignKey( - name: "fk_account_profiles_files_background_id", - table: "account_profiles", - column: "background_id", - principalTable: "files", - principalColumn: "id"); - - migrationBuilder.AddForeignKey( - name: "fk_account_profiles_files_picture_id", - table: "account_profiles", - column: "picture_id", - principalTable: "files", - principalColumn: "id"); - - migrationBuilder.AddForeignKey( - name: "fk_chat_rooms_files_background_id", - table: "chat_rooms", - column: "background_id", - principalTable: "files", - principalColumn: "id"); - - migrationBuilder.AddForeignKey( - name: "fk_chat_rooms_files_picture_id", - table: "chat_rooms", - column: "picture_id", - principalTable: "files", - principalColumn: "id"); - - migrationBuilder.AddForeignKey( - name: "fk_posts_posts_threaded_post_id", - table: "posts", - column: "threaded_post_id", - principalTable: "posts", - principalColumn: "id"); - - migrationBuilder.AddForeignKey( - name: "fk_publishers_files_background_id", - table: "publishers", - column: "background_id", - principalTable: "files", - principalColumn: "id"); - - migrationBuilder.AddForeignKey( - name: "fk_publishers_files_picture_id", - table: "publishers", - column: "picture_id", - principalTable: "files", - principalColumn: "id"); - - migrationBuilder.AddForeignKey( - name: "fk_realms_files_background_id", - table: "realms", - column: "background_id", - principalTable: "files", - principalColumn: "id"); - - migrationBuilder.AddForeignKey( - name: "fk_realms_files_picture_id", - table: "realms", - column: "picture_id", - principalTable: "files", - principalColumn: "id"); - - migrationBuilder.AddForeignKey( - name: "fk_stickers_files_image_id", - table: "stickers", - column: "image_id", - principalTable: "files", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - } - } -} diff --git a/DysonNetwork.Sphere/Migrations/20250602144445_FixPushNotificationIndex.Designer.cs b/DysonNetwork.Sphere/Migrations/20250602144445_FixPushNotificationIndex.Designer.cs deleted file mode 100644 index c90f7a8..0000000 --- a/DysonNetwork.Sphere/Migrations/20250602144445_FixPushNotificationIndex.Designer.cs +++ /dev/null @@ -1,3394 +0,0 @@ -// -using System; -using System.Collections.Generic; -using System.Text.Json; -using DysonNetwork.Sphere; -using DysonNetwork.Sphere.Account; -using DysonNetwork.Sphere.Storage; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using NetTopologySuite.Geometries; -using NodaTime; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; -using NpgsqlTypes; - -#nullable disable - -namespace DysonNetwork.Sphere.Migrations -{ - [DbContext(typeof(AppDatabase))] - [Migration("20250602144445_FixPushNotificationIndex")] - partial class FixPushNotificationIndex - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "9.0.3") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "postgis"); - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Account", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("ActivatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("activated_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("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("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.Sphere.Account.AccountAuthFactor", 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("Secret") - .HasMaxLength(8196) - .HasColumnType("character varying(8196)") - .HasColumnName("secret"); - - 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.Sphere.Account.AccountContact", 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("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.Sphere.Account.ActionLog", 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("geometry") - .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.HasIndex("SessionId") - .HasDatabaseName("ix_action_logs_session_id"); - - b.ToTable("action_logs", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Badge", 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("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.Sphere.Account.CheckInResult", 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("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.Sphere.Account.MagicSpell", 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.Sphere.Account.Notification", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("Content") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .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>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("Priority") - .HasColumnType("integer") - .HasColumnName("priority"); - - b.Property("Subtitle") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("subtitle"); - - b.Property("Title") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("title"); - - b.Property("Topic") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("topic"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("ViewedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("viewed_at"); - - b.HasKey("Id") - .HasName("pk_notifications"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_notifications_account_id"); - - b.ToTable("notifications", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.NotificationPushSubscription", 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(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("device_id"); - - b.Property("DeviceToken") - .IsRequired() - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("device_token"); - - b.Property("LastUsedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_used_at"); - - b.Property("Provider") - .HasColumnType("integer") - .HasColumnName("provider"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_notification_push_subscriptions"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_notification_push_subscriptions_account_id"); - - b.HasIndex("DeviceToken", "DeviceId", "AccountId") - .IsUnique() - .HasDatabaseName("ix_notification_push_subscriptions_device_token_device_id_acco"); - - b.ToTable("notification_push_subscriptions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Profile", 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("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_id"); - - 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("MiddleName") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("middle_name"); - - b.Property("Picture") - .HasColumnType("jsonb") - .HasColumnName("picture"); - - b.Property("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - b.Property("Pronouns") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("pronouns"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - 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.Sphere.Account.Relationship", 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.Sphere.Account.Status", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - 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("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("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.Sphere.Activity.Activity", 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>("Meta") - .IsRequired() - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("ResourceIdentifier") - .IsRequired() - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("resource_identifier"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property>("UsersVisible") - .IsRequired() - .HasColumnType("jsonb") - .HasColumnName("users_visible"); - - b.Property("Visibility") - .HasColumnType("integer") - .HasColumnName("visibility"); - - b.HasKey("Id") - .HasName("pk_activities"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_activities_account_id"); - - b.ToTable("activities", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Challenge", 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") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("device_id"); - - 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("geometry") - .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("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_challenges"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_auth_challenges_account_id"); - - b.ToTable("auth_challenges", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Session", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("ChallengeId") - .HasColumnType("uuid") - .HasColumnName("challenge_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("Label") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("label"); - - b.Property("LastGrantedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_granted_at"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - 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.ToTable("auth_sessions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatMember", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("ChatRoomId") - .HasColumnType("uuid") - .HasColumnName("chat_room_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("IsBot") - .HasColumnType("boolean") - .HasColumnName("is_bot"); - - b.Property("JoinedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("joined_at"); - - b.Property("LastReadAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_read_at"); - - b.Property("LeaveAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("leave_at"); - - b.Property("Nick") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("nick"); - - b.Property("Notify") - .HasColumnType("integer") - .HasColumnName("notify"); - - b.Property("Role") - .HasColumnType("integer") - .HasColumnName("role"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_chat_members"); - - b.HasAlternateKey("ChatRoomId", "AccountId") - .HasName("ak_chat_members_chat_room_id_account_id"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_chat_members_account_id"); - - b.ToTable("chat_members", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatRoom", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("Background") - .HasColumnType("jsonb") - .HasColumnName("background"); - - b.Property("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_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("Description") - .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") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("Picture") - .HasColumnType("jsonb") - .HasColumnName("picture"); - - b.Property("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - b.Property("RealmId") - .HasColumnType("uuid") - .HasColumnName("realm_id"); - - b.Property("Type") - .HasColumnType("integer") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_chat_rooms"); - - b.HasIndex("RealmId") - .HasDatabaseName("ix_chat_rooms_realm_id"); - - b.ToTable("chat_rooms", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.Message", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property>("Attachments") - .IsRequired() - .HasColumnType("jsonb") - .HasColumnName("attachments"); - - b.Property("ChatRoomId") - .HasColumnType("uuid") - .HasColumnName("chat_room_id"); - - b.Property("Content") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .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("EditedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("edited_at"); - - b.Property("ForwardedMessageId") - .HasColumnType("uuid") - .HasColumnName("forwarded_message_id"); - - b.Property>("MembersMentioned") - .HasColumnType("jsonb") - .HasColumnName("members_mentioned"); - - b.Property>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("Nonce") - .IsRequired() - .HasMaxLength(36) - .HasColumnType("character varying(36)") - .HasColumnName("nonce"); - - b.Property("RepliedMessageId") - .HasColumnType("uuid") - .HasColumnName("replied_message_id"); - - b.Property("SenderId") - .HasColumnType("uuid") - .HasColumnName("sender_id"); - - 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_chat_messages"); - - b.HasIndex("ChatRoomId") - .HasDatabaseName("ix_chat_messages_chat_room_id"); - - b.HasIndex("ForwardedMessageId") - .HasDatabaseName("ix_chat_messages_forwarded_message_id"); - - b.HasIndex("RepliedMessageId") - .HasDatabaseName("ix_chat_messages_replied_message_id"); - - b.HasIndex("SenderId") - .HasDatabaseName("ix_chat_messages_sender_id"); - - b.ToTable("chat_messages", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.MessageReaction", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("Attitude") - .HasColumnType("integer") - .HasColumnName("attitude"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("MessageId") - .HasColumnType("uuid") - .HasColumnName("message_id"); - - b.Property("SenderId") - .HasColumnType("uuid") - .HasColumnName("sender_id"); - - b.Property("Symbol") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("symbol"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_chat_reactions"); - - b.HasIndex("MessageId") - .HasDatabaseName("ix_chat_reactions_message_id"); - - b.HasIndex("SenderId") - .HasDatabaseName("ix_chat_reactions_sender_id"); - - b.ToTable("chat_reactions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.RealtimeCall", 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("EndedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("ended_at"); - - b.Property("ProviderName") - .HasColumnType("text") - .HasColumnName("provider_name"); - - b.Property("RoomId") - .HasColumnType("uuid") - .HasColumnName("room_id"); - - b.Property("SenderId") - .HasColumnType("uuid") - .HasColumnName("sender_id"); - - b.Property("SessionId") - .HasColumnType("text") - .HasColumnName("session_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("UpstreamConfigJson") - .HasColumnType("jsonb") - .HasColumnName("upstream"); - - b.HasKey("Id") - .HasName("pk_chat_realtime_call"); - - b.HasIndex("RoomId") - .HasDatabaseName("ix_chat_realtime_call_room_id"); - - b.HasIndex("SenderId") - .HasDatabaseName("ix_chat_realtime_call_sender_id"); - - b.ToTable("chat_realtime_call", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomApp", 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("Name") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("slug"); - - b.Property("Status") - .HasColumnType("integer") - .HasColumnName("status"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("VerifiedAs") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("verified_as"); - - b.Property("VerifiedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("verified_at"); - - b.HasKey("Id") - .HasName("pk_custom_apps"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_custom_apps_publisher_id"); - - b.ToTable("custom_apps", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomAppSecret", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AppId") - .HasColumnType("uuid") - .HasColumnName("app_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("Remarks") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("remarks"); - - b.Property("Secret") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("secret"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_custom_app_secrets"); - - b.HasIndex("AppId") - .HasDatabaseName("ix_custom_app_secrets_app_id"); - - b.ToTable("custom_app_secrets", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroup", 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.Sphere.Permission.PermissionGroupMember", 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.Sphere.Permission.PermissionNode", 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("Area") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("area"); - - 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("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", "Area", "Actor") - .HasDatabaseName("ix_permission_nodes_key_area_actor"); - - b.ToTable("permission_nodes", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.Post", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property>("Attachments") - .IsRequired() - .HasColumnType("jsonb") - .HasColumnName("attachments"); - - b.Property("Content") - .HasColumnType("text") - .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("Description") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("Downvotes") - .HasColumnType("integer") - .HasColumnName("downvotes"); - - b.Property("EditedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("edited_at"); - - b.Property("ForwardedPostId") - .HasColumnType("uuid") - .HasColumnName("forwarded_post_id"); - - b.Property("Language") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("language"); - - b.Property>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("PublishedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("published_at"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("RepliedPostId") - .HasColumnType("uuid") - .HasColumnName("replied_post_id"); - - b.Property("SearchVector") - .IsRequired() - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("tsvector") - .HasColumnName("search_vector") - .HasAnnotation("Npgsql:TsVectorConfig", "simple") - .HasAnnotation("Npgsql:TsVectorProperties", new[] { "Title", "Description", "Content" }); - - b.Property("Title") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("title"); - - b.Property("Type") - .HasColumnType("integer") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("Upvotes") - .HasColumnType("integer") - .HasColumnName("upvotes"); - - b.Property("ViewsTotal") - .HasColumnType("integer") - .HasColumnName("views_total"); - - b.Property("ViewsUnique") - .HasColumnType("integer") - .HasColumnName("views_unique"); - - b.Property("Visibility") - .HasColumnType("integer") - .HasColumnName("visibility"); - - b.HasKey("Id") - .HasName("pk_posts"); - - b.HasIndex("ForwardedPostId") - .HasDatabaseName("ix_posts_forwarded_post_id"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_posts_publisher_id"); - - b.HasIndex("RepliedPostId") - .HasDatabaseName("ix_posts_replied_post_id"); - - b.HasIndex("SearchVector") - .HasDatabaseName("ix_posts_search_vector"); - - NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex("SearchVector"), "GIN"); - - b.ToTable("posts", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostCategory", 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("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("name"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_categories"); - - b.ToTable("post_categories", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostCollection", 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("Description") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("name"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_collections"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_post_collections_publisher_id"); - - b.ToTable("post_collections", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostReaction", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("Attitude") - .HasColumnType("integer") - .HasColumnName("attitude"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("PostId") - .HasColumnType("uuid") - .HasColumnName("post_id"); - - b.Property("Symbol") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("symbol"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_reactions"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_post_reactions_account_id"); - - b.HasIndex("PostId") - .HasDatabaseName("ix_post_reactions_post_id"); - - b.ToTable("post_reactions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostTag", 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("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("name"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_tags"); - - b.ToTable("post_tags", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.Publisher", 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("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_id"); - - b.Property("Bio") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("bio"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - 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("Picture") - .HasColumnType("jsonb") - .HasColumnName("picture"); - - b.Property("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - b.Property("RealmId") - .HasColumnType("uuid") - .HasColumnName("realm_id"); - - b.Property("Type") - .HasColumnType("integer") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_publishers"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_publishers_account_id"); - - b.HasIndex("Name") - .IsUnique() - .HasDatabaseName("ix_publishers_name"); - - b.HasIndex("RealmId") - .HasDatabaseName("ix_publishers_realm_id"); - - b.ToTable("publishers", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherFeature", 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("ExpiredAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expired_at"); - - b.Property("Flag") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("flag"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_publisher_features"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_publisher_features_publisher_id"); - - b.ToTable("publisher_features", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherMember", b => - { - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_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("Role") - .HasColumnType("integer") - .HasColumnName("role"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("PublisherId", "AccountId") - .HasName("pk_publisher_members"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_publisher_members_account_id"); - - b.ToTable("publisher_members", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherSubscription", 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("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("Status") - .HasColumnType("integer") - .HasColumnName("status"); - - b.Property("Tier") - .HasColumnType("integer") - .HasColumnName("tier"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_publisher_subscriptions"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_publisher_subscriptions_account_id"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_publisher_subscriptions_publisher_id"); - - b.ToTable("publisher_subscriptions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.Realm", 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("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_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("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("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - 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("VerifiedAs") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("verified_as"); - - b.Property("VerifiedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("verified_at"); - - b.HasKey("Id") - .HasName("pk_realms"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_realms_account_id"); - - b.HasIndex("Slug") - .IsUnique() - .HasDatabaseName("ix_realms_slug"); - - b.ToTable("realms", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.RealmMember", 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.HasIndex("AccountId") - .HasDatabaseName("ix_realm_members_account_id"); - - b.ToTable("realm_members", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.Sticker", 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("Image") - .HasColumnType("jsonb") - .HasColumnName("image"); - - b.Property("ImageId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("image_id"); - - b.Property("PackId") - .HasColumnType("uuid") - .HasColumnName("pack_id"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_stickers"); - - b.HasIndex("PackId") - .HasDatabaseName("ix_stickers_pack_id"); - - b.HasIndex("Slug") - .HasDatabaseName("ix_stickers_slug"); - - b.ToTable("stickers", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.StickerPack", 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("Description") - .IsRequired() - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("Prefix") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("prefix"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_sticker_packs"); - - b.HasIndex("Prefix") - .IsUnique() - .HasDatabaseName("ix_sticker_packs_prefix"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_sticker_packs_publisher_id"); - - b.ToTable("sticker_packs", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFile", b => - { - b.Property("Id") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .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("Description") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property>("FileMeta") - .HasColumnType("jsonb") - .HasColumnName("file_meta"); - - b.Property("HasCompression") - .HasColumnType("boolean") - .HasColumnName("has_compression"); - - b.Property("Hash") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("hash"); - - b.Property("MessageId") - .HasColumnType("uuid") - .HasColumnName("message_id"); - - b.Property("MimeType") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("mime_type"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("PostId") - .HasColumnType("uuid") - .HasColumnName("post_id"); - - b.Property>("SensitiveMarks") - .HasColumnType("jsonb") - .HasColumnName("sensitive_marks"); - - b.Property("Size") - .HasColumnType("bigint") - .HasColumnName("size"); - - b.Property("StorageId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("storage_id"); - - b.Property("StorageUrl") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("storage_url"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("UploadedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("uploaded_at"); - - b.Property("UploadedTo") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("uploaded_to"); - - b.Property>("UserMeta") - .HasColumnType("jsonb") - .HasColumnName("user_meta"); - - b.HasKey("Id") - .HasName("pk_files"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_files_account_id"); - - b.HasIndex("MessageId") - .HasDatabaseName("ix_files_message_id"); - - b.HasIndex("PostId") - .HasDatabaseName("ix_files_post_id"); - - b.ToTable("files", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFileReference", 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("ExpiredAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expired_at"); - - b.Property("FileId") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("file_id"); - - b.Property("ResourceId") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("resource_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("Usage") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("usage"); - - b.HasKey("Id") - .HasName("pk_file_references"); - - b.HasIndex("FileId") - .HasDatabaseName("ix_file_references_file_id"); - - b.ToTable("file_references", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Order", 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("ExpiredAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expired_at"); - - b.Property("IssuerAppId") - .HasColumnType("uuid") - .HasColumnName("issuer_app_id"); - - b.Property("PayeeWalletId") - .HasColumnType("uuid") - .HasColumnName("payee_wallet_id"); - - 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("IssuerAppId") - .HasDatabaseName("ix_payment_orders_issuer_app_id"); - - 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.Sphere.Wallet.Transaction", 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.Sphere.Wallet.Wallet", 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.Sphere.Wallet.WalletPocket", 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("PostPostCategory", b => - { - b.Property("CategoriesId") - .HasColumnType("uuid") - .HasColumnName("categories_id"); - - b.Property("PostsId") - .HasColumnType("uuid") - .HasColumnName("posts_id"); - - b.HasKey("CategoriesId", "PostsId") - .HasName("pk_post_category_links"); - - b.HasIndex("PostsId") - .HasDatabaseName("ix_post_category_links_posts_id"); - - b.ToTable("post_category_links", (string)null); - }); - - modelBuilder.Entity("PostPostCollection", b => - { - b.Property("CollectionsId") - .HasColumnType("uuid") - .HasColumnName("collections_id"); - - b.Property("PostsId") - .HasColumnType("uuid") - .HasColumnName("posts_id"); - - b.HasKey("CollectionsId", "PostsId") - .HasName("pk_post_collection_links"); - - b.HasIndex("PostsId") - .HasDatabaseName("ix_post_collection_links_posts_id"); - - b.ToTable("post_collection_links", (string)null); - }); - - modelBuilder.Entity("PostPostTag", b => - { - b.Property("PostsId") - .HasColumnType("uuid") - .HasColumnName("posts_id"); - - b.Property("TagsId") - .HasColumnType("uuid") - .HasColumnName("tags_id"); - - b.HasKey("PostsId", "TagsId") - .HasName("pk_post_tag_links"); - - b.HasIndex("TagsId") - .HasDatabaseName("ix_post_tag_links_tags_id"); - - b.ToTable("post_tag_links", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.AccountAuthFactor", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("AuthFactors") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_auth_factors_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.AccountContact", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Contacts") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_contacts_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.ActionLog", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_action_logs_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Auth.Session", "Session") - .WithMany() - .HasForeignKey("SessionId") - .HasConstraintName("fk_action_logs_auth_sessions_session_id"); - - b.Navigation("Account"); - - b.Navigation("Session"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Badge", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Badges") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_badges_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.CheckInResult", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_check_in_results_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.MagicSpell", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .HasConstraintName("fk_magic_spells_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Notification", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_notifications_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.NotificationPushSubscription", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_notification_push_subscriptions_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Profile", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithOne("Profile") - .HasForeignKey("DysonNetwork.Sphere.Account.Profile", "AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_profiles_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Relationship", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("OutgoingRelationships") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_relationships_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Account.Account", "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.Sphere.Account.Status", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_statuses_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Activity.Activity", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_activities_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Challenge", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Challenges") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_challenges_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Session", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Sessions") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_sessions_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Auth.Challenge", "Challenge") - .WithMany() - .HasForeignKey("ChallengeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_sessions_auth_challenges_challenge_id"); - - b.Navigation("Account"); - - b.Navigation("Challenge"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatMember", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_members_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatRoom", "ChatRoom") - .WithMany("Members") - .HasForeignKey("ChatRoomId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_members_chat_rooms_chat_room_id"); - - b.Navigation("Account"); - - b.Navigation("ChatRoom"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatRoom", b => - { - b.HasOne("DysonNetwork.Sphere.Realm.Realm", "Realm") - .WithMany("ChatRooms") - .HasForeignKey("RealmId") - .HasConstraintName("fk_chat_rooms_realms_realm_id"); - - b.Navigation("Realm"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.Message", b => - { - b.HasOne("DysonNetwork.Sphere.Chat.ChatRoom", "ChatRoom") - .WithMany() - .HasForeignKey("ChatRoomId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_messages_chat_rooms_chat_room_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.Message", "ForwardedMessage") - .WithMany() - .HasForeignKey("ForwardedMessageId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_chat_messages_chat_messages_forwarded_message_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.Message", "RepliedMessage") - .WithMany() - .HasForeignKey("RepliedMessageId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_chat_messages_chat_messages_replied_message_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatMember", "Sender") - .WithMany() - .HasForeignKey("SenderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_messages_chat_members_sender_id"); - - b.Navigation("ChatRoom"); - - b.Navigation("ForwardedMessage"); - - b.Navigation("RepliedMessage"); - - b.Navigation("Sender"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.MessageReaction", b => - { - b.HasOne("DysonNetwork.Sphere.Chat.Message", "Message") - .WithMany("Reactions") - .HasForeignKey("MessageId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_reactions_chat_messages_message_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatMember", "Sender") - .WithMany() - .HasForeignKey("SenderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_reactions_chat_members_sender_id"); - - b.Navigation("Message"); - - b.Navigation("Sender"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.RealtimeCall", b => - { - b.HasOne("DysonNetwork.Sphere.Chat.ChatRoom", "Room") - .WithMany() - .HasForeignKey("RoomId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_realtime_call_chat_rooms_room_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatMember", "Sender") - .WithMany() - .HasForeignKey("SenderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_realtime_call_chat_members_sender_id"); - - b.Navigation("Room"); - - b.Navigation("Sender"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomApp", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Developer") - .WithMany() - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_custom_apps_publishers_publisher_id"); - - b.Navigation("Developer"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomAppSecret", b => - { - b.HasOne("DysonNetwork.Sphere.Developer.CustomApp", "App") - .WithMany() - .HasForeignKey("AppId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_custom_app_secrets_custom_apps_app_id"); - - b.Navigation("App"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroupMember", b => - { - b.HasOne("DysonNetwork.Sphere.Permission.PermissionGroup", "Group") - .WithMany("Members") - .HasForeignKey("GroupId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_permission_group_members_permission_groups_group_id"); - - b.Navigation("Group"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionNode", b => - { - b.HasOne("DysonNetwork.Sphere.Permission.PermissionGroup", "Group") - .WithMany("Nodes") - .HasForeignKey("GroupId") - .HasConstraintName("fk_permission_nodes_permission_groups_group_id"); - - b.Navigation("Group"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.Post", b => - { - b.HasOne("DysonNetwork.Sphere.Post.Post", "ForwardedPost") - .WithMany() - .HasForeignKey("ForwardedPostId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_posts_posts_forwarded_post_id"); - - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Posts") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_posts_publishers_publisher_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", "RepliedPost") - .WithMany() - .HasForeignKey("RepliedPostId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_posts_posts_replied_post_id"); - - b.Navigation("ForwardedPost"); - - b.Navigation("Publisher"); - - b.Navigation("RepliedPost"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostCollection", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Collections") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_collections_publishers_publisher_id"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostReaction", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_reactions_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", "Post") - .WithMany("Reactions") - .HasForeignKey("PostId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_reactions_posts_post_id"); - - b.Navigation("Account"); - - b.Navigation("Post"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.Publisher", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .HasConstraintName("fk_publishers_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Realm.Realm", "Realm") - .WithMany() - .HasForeignKey("RealmId") - .HasConstraintName("fk_publishers_realms_realm_id"); - - b.Navigation("Account"); - - b.Navigation("Realm"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherFeature", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany() - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_features_publishers_publisher_id"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherMember", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_members_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Members") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_members_publishers_publisher_id"); - - b.Navigation("Account"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherSubscription", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_subscriptions_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Subscriptions") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_subscriptions_publishers_publisher_id"); - - b.Navigation("Account"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.Realm", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_realms_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.RealmMember", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_realm_members_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Realm.Realm", "Realm") - .WithMany("Members") - .HasForeignKey("RealmId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_realm_members_realms_realm_id"); - - b.Navigation("Account"); - - b.Navigation("Realm"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.Sticker", b => - { - b.HasOne("DysonNetwork.Sphere.Sticker.StickerPack", "Pack") - .WithMany() - .HasForeignKey("PackId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_stickers_sticker_packs_pack_id"); - - b.Navigation("Pack"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.StickerPack", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany() - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_sticker_packs_publishers_publisher_id"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFile", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_files_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.Message", null) - .WithMany("OutdatedAttachments") - .HasForeignKey("MessageId") - .HasConstraintName("fk_files_chat_messages_message_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany("OutdatedAttachments") - .HasForeignKey("PostId") - .HasConstraintName("fk_files_posts_post_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFileReference", b => - { - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "File") - .WithMany() - .HasForeignKey("FileId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_file_references_files_file_id"); - - b.Navigation("File"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Order", b => - { - b.HasOne("DysonNetwork.Sphere.Developer.CustomApp", "IssuerApp") - .WithMany() - .HasForeignKey("IssuerAppId") - .HasConstraintName("fk_payment_orders_custom_apps_issuer_app_id"); - - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "PayeeWallet") - .WithMany() - .HasForeignKey("PayeeWalletId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_payment_orders_wallets_payee_wallet_id"); - - b.HasOne("DysonNetwork.Sphere.Wallet.Transaction", "Transaction") - .WithMany() - .HasForeignKey("TransactionId") - .HasConstraintName("fk_payment_orders_payment_transactions_transaction_id"); - - b.Navigation("IssuerApp"); - - b.Navigation("PayeeWallet"); - - b.Navigation("Transaction"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Transaction", b => - { - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "PayeeWallet") - .WithMany() - .HasForeignKey("PayeeWalletId") - .HasConstraintName("fk_payment_transactions_wallets_payee_wallet_id"); - - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "PayerWallet") - .WithMany() - .HasForeignKey("PayerWalletId") - .HasConstraintName("fk_payment_transactions_wallets_payer_wallet_id"); - - b.Navigation("PayeeWallet"); - - b.Navigation("PayerWallet"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Wallet", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_wallets_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.WalletPocket", b => - { - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "Wallet") - .WithMany("Pockets") - .HasForeignKey("WalletId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_wallet_pockets_wallets_wallet_id"); - - b.Navigation("Wallet"); - }); - - modelBuilder.Entity("PostPostCategory", b => - { - b.HasOne("DysonNetwork.Sphere.Post.PostCategory", null) - .WithMany() - .HasForeignKey("CategoriesId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_category_links_post_categories_categories_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany() - .HasForeignKey("PostsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_category_links_posts_posts_id"); - }); - - modelBuilder.Entity("PostPostCollection", b => - { - b.HasOne("DysonNetwork.Sphere.Post.PostCollection", null) - .WithMany() - .HasForeignKey("CollectionsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_collection_links_post_collections_collections_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany() - .HasForeignKey("PostsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_collection_links_posts_posts_id"); - }); - - modelBuilder.Entity("PostPostTag", b => - { - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany() - .HasForeignKey("PostsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_tag_links_posts_posts_id"); - - b.HasOne("DysonNetwork.Sphere.Post.PostTag", null) - .WithMany() - .HasForeignKey("TagsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_tag_links_post_tags_tags_id"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Account", b => - { - b.Navigation("AuthFactors"); - - b.Navigation("Badges"); - - b.Navigation("Challenges"); - - b.Navigation("Contacts"); - - b.Navigation("IncomingRelationships"); - - b.Navigation("OutgoingRelationships"); - - b.Navigation("Profile") - .IsRequired(); - - b.Navigation("Sessions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatRoom", b => - { - b.Navigation("Members"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.Message", b => - { - b.Navigation("OutdatedAttachments"); - - b.Navigation("Reactions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroup", b => - { - b.Navigation("Members"); - - b.Navigation("Nodes"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.Post", b => - { - b.Navigation("OutdatedAttachments"); - - b.Navigation("Reactions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.Publisher", b => - { - b.Navigation("Collections"); - - b.Navigation("Members"); - - b.Navigation("Posts"); - - b.Navigation("Subscriptions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.Realm", b => - { - b.Navigation("ChatRooms"); - - b.Navigation("Members"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Wallet", b => - { - b.Navigation("Pockets"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/DysonNetwork.Sphere/Migrations/20250602144445_FixPushNotificationIndex.cs b/DysonNetwork.Sphere/Migrations/20250602144445_FixPushNotificationIndex.cs deleted file mode 100644 index bcfa0d9..0000000 --- a/DysonNetwork.Sphere/Migrations/20250602144445_FixPushNotificationIndex.cs +++ /dev/null @@ -1,38 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace DysonNetwork.Sphere.Migrations -{ - /// - public partial class FixPushNotificationIndex : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropIndex( - name: "ix_notification_push_subscriptions_device_token_device_id", - table: "notification_push_subscriptions"); - - migrationBuilder.CreateIndex( - name: "ix_notification_push_subscriptions_device_token_device_id_acco", - table: "notification_push_subscriptions", - columns: new[] { "device_token", "device_id", "account_id" }, - unique: true); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropIndex( - name: "ix_notification_push_subscriptions_device_token_device_id_acco", - table: "notification_push_subscriptions"); - - migrationBuilder.CreateIndex( - name: "ix_notification_push_subscriptions_device_token_device_id", - table: "notification_push_subscriptions", - columns: new[] { "device_token", "device_id" }, - unique: true); - } - } -} diff --git a/DysonNetwork.Sphere/Migrations/20250603153937_BetterAuthFactor.Designer.cs b/DysonNetwork.Sphere/Migrations/20250603153937_BetterAuthFactor.Designer.cs deleted file mode 100644 index 9e08655..0000000 --- a/DysonNetwork.Sphere/Migrations/20250603153937_BetterAuthFactor.Designer.cs +++ /dev/null @@ -1,3410 +0,0 @@ -// -using System; -using System.Collections.Generic; -using System.Text.Json; -using DysonNetwork.Sphere; -using DysonNetwork.Sphere.Account; -using DysonNetwork.Sphere.Storage; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using NetTopologySuite.Geometries; -using NodaTime; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; -using NpgsqlTypes; - -#nullable disable - -namespace DysonNetwork.Sphere.Migrations -{ - [DbContext(typeof(AppDatabase))] - [Migration("20250603153937_BetterAuthFactor")] - partial class BetterAuthFactor - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "9.0.3") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "postgis"); - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Account", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("ActivatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("activated_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("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("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.Sphere.Account.AccountAuthFactor", 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.Sphere.Account.AccountContact", 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("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.Sphere.Account.ActionLog", 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("geometry") - .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.HasIndex("SessionId") - .HasDatabaseName("ix_action_logs_session_id"); - - b.ToTable("action_logs", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Badge", 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("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.Sphere.Account.CheckInResult", 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("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.Sphere.Account.MagicSpell", 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.Sphere.Account.Notification", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("Content") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .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>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("Priority") - .HasColumnType("integer") - .HasColumnName("priority"); - - b.Property("Subtitle") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("subtitle"); - - b.Property("Title") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("title"); - - b.Property("Topic") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("topic"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("ViewedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("viewed_at"); - - b.HasKey("Id") - .HasName("pk_notifications"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_notifications_account_id"); - - b.ToTable("notifications", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.NotificationPushSubscription", 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(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("device_id"); - - b.Property("DeviceToken") - .IsRequired() - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("device_token"); - - b.Property("LastUsedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_used_at"); - - b.Property("Provider") - .HasColumnType("integer") - .HasColumnName("provider"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_notification_push_subscriptions"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_notification_push_subscriptions_account_id"); - - b.HasIndex("DeviceToken", "DeviceId", "AccountId") - .IsUnique() - .HasDatabaseName("ix_notification_push_subscriptions_device_token_device_id_acco"); - - b.ToTable("notification_push_subscriptions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Profile", 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("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_id"); - - 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("MiddleName") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("middle_name"); - - b.Property("Picture") - .HasColumnType("jsonb") - .HasColumnName("picture"); - - b.Property("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - b.Property("Pronouns") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("pronouns"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - 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.Sphere.Account.Relationship", 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.Sphere.Account.Status", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - 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("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("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.Sphere.Activity.Activity", 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>("Meta") - .IsRequired() - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("ResourceIdentifier") - .IsRequired() - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("resource_identifier"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property>("UsersVisible") - .IsRequired() - .HasColumnType("jsonb") - .HasColumnName("users_visible"); - - b.Property("Visibility") - .HasColumnType("integer") - .HasColumnName("visibility"); - - b.HasKey("Id") - .HasName("pk_activities"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_activities_account_id"); - - b.ToTable("activities", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Challenge", 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") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("device_id"); - - 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("geometry") - .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("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_challenges"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_auth_challenges_account_id"); - - b.ToTable("auth_challenges", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Session", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("ChallengeId") - .HasColumnType("uuid") - .HasColumnName("challenge_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("Label") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("label"); - - b.Property("LastGrantedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_granted_at"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - 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.ToTable("auth_sessions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatMember", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("ChatRoomId") - .HasColumnType("uuid") - .HasColumnName("chat_room_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("IsBot") - .HasColumnType("boolean") - .HasColumnName("is_bot"); - - b.Property("JoinedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("joined_at"); - - b.Property("LastReadAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_read_at"); - - b.Property("LeaveAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("leave_at"); - - b.Property("Nick") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("nick"); - - b.Property("Notify") - .HasColumnType("integer") - .HasColumnName("notify"); - - b.Property("Role") - .HasColumnType("integer") - .HasColumnName("role"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_chat_members"); - - b.HasAlternateKey("ChatRoomId", "AccountId") - .HasName("ak_chat_members_chat_room_id_account_id"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_chat_members_account_id"); - - b.ToTable("chat_members", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatRoom", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("Background") - .HasColumnType("jsonb") - .HasColumnName("background"); - - b.Property("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_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("Description") - .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") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("Picture") - .HasColumnType("jsonb") - .HasColumnName("picture"); - - b.Property("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - b.Property("RealmId") - .HasColumnType("uuid") - .HasColumnName("realm_id"); - - b.Property("Type") - .HasColumnType("integer") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_chat_rooms"); - - b.HasIndex("RealmId") - .HasDatabaseName("ix_chat_rooms_realm_id"); - - b.ToTable("chat_rooms", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.Message", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property>("Attachments") - .IsRequired() - .HasColumnType("jsonb") - .HasColumnName("attachments"); - - b.Property("ChatRoomId") - .HasColumnType("uuid") - .HasColumnName("chat_room_id"); - - b.Property("Content") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .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("EditedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("edited_at"); - - b.Property("ForwardedMessageId") - .HasColumnType("uuid") - .HasColumnName("forwarded_message_id"); - - b.Property>("MembersMentioned") - .HasColumnType("jsonb") - .HasColumnName("members_mentioned"); - - b.Property>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("Nonce") - .IsRequired() - .HasMaxLength(36) - .HasColumnType("character varying(36)") - .HasColumnName("nonce"); - - b.Property("RepliedMessageId") - .HasColumnType("uuid") - .HasColumnName("replied_message_id"); - - b.Property("SenderId") - .HasColumnType("uuid") - .HasColumnName("sender_id"); - - 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_chat_messages"); - - b.HasIndex("ChatRoomId") - .HasDatabaseName("ix_chat_messages_chat_room_id"); - - b.HasIndex("ForwardedMessageId") - .HasDatabaseName("ix_chat_messages_forwarded_message_id"); - - b.HasIndex("RepliedMessageId") - .HasDatabaseName("ix_chat_messages_replied_message_id"); - - b.HasIndex("SenderId") - .HasDatabaseName("ix_chat_messages_sender_id"); - - b.ToTable("chat_messages", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.MessageReaction", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("Attitude") - .HasColumnType("integer") - .HasColumnName("attitude"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("MessageId") - .HasColumnType("uuid") - .HasColumnName("message_id"); - - b.Property("SenderId") - .HasColumnType("uuid") - .HasColumnName("sender_id"); - - b.Property("Symbol") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("symbol"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_chat_reactions"); - - b.HasIndex("MessageId") - .HasDatabaseName("ix_chat_reactions_message_id"); - - b.HasIndex("SenderId") - .HasDatabaseName("ix_chat_reactions_sender_id"); - - b.ToTable("chat_reactions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.RealtimeCall", 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("EndedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("ended_at"); - - b.Property("ProviderName") - .HasColumnType("text") - .HasColumnName("provider_name"); - - b.Property("RoomId") - .HasColumnType("uuid") - .HasColumnName("room_id"); - - b.Property("SenderId") - .HasColumnType("uuid") - .HasColumnName("sender_id"); - - b.Property("SessionId") - .HasColumnType("text") - .HasColumnName("session_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("UpstreamConfigJson") - .HasColumnType("jsonb") - .HasColumnName("upstream"); - - b.HasKey("Id") - .HasName("pk_chat_realtime_call"); - - b.HasIndex("RoomId") - .HasDatabaseName("ix_chat_realtime_call_room_id"); - - b.HasIndex("SenderId") - .HasDatabaseName("ix_chat_realtime_call_sender_id"); - - b.ToTable("chat_realtime_call", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomApp", 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("Name") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("slug"); - - b.Property("Status") - .HasColumnType("integer") - .HasColumnName("status"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("VerifiedAs") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("verified_as"); - - b.Property("VerifiedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("verified_at"); - - b.HasKey("Id") - .HasName("pk_custom_apps"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_custom_apps_publisher_id"); - - b.ToTable("custom_apps", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomAppSecret", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AppId") - .HasColumnType("uuid") - .HasColumnName("app_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("Remarks") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("remarks"); - - b.Property("Secret") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("secret"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_custom_app_secrets"); - - b.HasIndex("AppId") - .HasDatabaseName("ix_custom_app_secrets_app_id"); - - b.ToTable("custom_app_secrets", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroup", 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.Sphere.Permission.PermissionGroupMember", 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.Sphere.Permission.PermissionNode", 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("Area") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("area"); - - 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("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", "Area", "Actor") - .HasDatabaseName("ix_permission_nodes_key_area_actor"); - - b.ToTable("permission_nodes", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.Post", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property>("Attachments") - .IsRequired() - .HasColumnType("jsonb") - .HasColumnName("attachments"); - - b.Property("Content") - .HasColumnType("text") - .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("Description") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("Downvotes") - .HasColumnType("integer") - .HasColumnName("downvotes"); - - b.Property("EditedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("edited_at"); - - b.Property("ForwardedPostId") - .HasColumnType("uuid") - .HasColumnName("forwarded_post_id"); - - b.Property("Language") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("language"); - - b.Property>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("PublishedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("published_at"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("RepliedPostId") - .HasColumnType("uuid") - .HasColumnName("replied_post_id"); - - b.Property("SearchVector") - .IsRequired() - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("tsvector") - .HasColumnName("search_vector") - .HasAnnotation("Npgsql:TsVectorConfig", "simple") - .HasAnnotation("Npgsql:TsVectorProperties", new[] { "Title", "Description", "Content" }); - - b.Property("Title") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("title"); - - b.Property("Type") - .HasColumnType("integer") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("Upvotes") - .HasColumnType("integer") - .HasColumnName("upvotes"); - - b.Property("ViewsTotal") - .HasColumnType("integer") - .HasColumnName("views_total"); - - b.Property("ViewsUnique") - .HasColumnType("integer") - .HasColumnName("views_unique"); - - b.Property("Visibility") - .HasColumnType("integer") - .HasColumnName("visibility"); - - b.HasKey("Id") - .HasName("pk_posts"); - - b.HasIndex("ForwardedPostId") - .HasDatabaseName("ix_posts_forwarded_post_id"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_posts_publisher_id"); - - b.HasIndex("RepliedPostId") - .HasDatabaseName("ix_posts_replied_post_id"); - - b.HasIndex("SearchVector") - .HasDatabaseName("ix_posts_search_vector"); - - NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex("SearchVector"), "GIN"); - - b.ToTable("posts", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostCategory", 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("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("name"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_categories"); - - b.ToTable("post_categories", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostCollection", 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("Description") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("name"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_collections"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_post_collections_publisher_id"); - - b.ToTable("post_collections", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostReaction", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("Attitude") - .HasColumnType("integer") - .HasColumnName("attitude"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("PostId") - .HasColumnType("uuid") - .HasColumnName("post_id"); - - b.Property("Symbol") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("symbol"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_reactions"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_post_reactions_account_id"); - - b.HasIndex("PostId") - .HasDatabaseName("ix_post_reactions_post_id"); - - b.ToTable("post_reactions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostTag", 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("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("name"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_tags"); - - b.ToTable("post_tags", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.Publisher", 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("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_id"); - - b.Property("Bio") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("bio"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - 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("Picture") - .HasColumnType("jsonb") - .HasColumnName("picture"); - - b.Property("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - b.Property("RealmId") - .HasColumnType("uuid") - .HasColumnName("realm_id"); - - b.Property("Type") - .HasColumnType("integer") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_publishers"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_publishers_account_id"); - - b.HasIndex("Name") - .IsUnique() - .HasDatabaseName("ix_publishers_name"); - - b.HasIndex("RealmId") - .HasDatabaseName("ix_publishers_realm_id"); - - b.ToTable("publishers", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherFeature", 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("ExpiredAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expired_at"); - - b.Property("Flag") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("flag"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_publisher_features"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_publisher_features_publisher_id"); - - b.ToTable("publisher_features", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherMember", b => - { - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_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("Role") - .HasColumnType("integer") - .HasColumnName("role"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("PublisherId", "AccountId") - .HasName("pk_publisher_members"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_publisher_members_account_id"); - - b.ToTable("publisher_members", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherSubscription", 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("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("Status") - .HasColumnType("integer") - .HasColumnName("status"); - - b.Property("Tier") - .HasColumnType("integer") - .HasColumnName("tier"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_publisher_subscriptions"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_publisher_subscriptions_account_id"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_publisher_subscriptions_publisher_id"); - - b.ToTable("publisher_subscriptions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.Realm", 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("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_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("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("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - 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("VerifiedAs") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("verified_as"); - - b.Property("VerifiedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("verified_at"); - - b.HasKey("Id") - .HasName("pk_realms"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_realms_account_id"); - - b.HasIndex("Slug") - .IsUnique() - .HasDatabaseName("ix_realms_slug"); - - b.ToTable("realms", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.RealmMember", 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.HasIndex("AccountId") - .HasDatabaseName("ix_realm_members_account_id"); - - b.ToTable("realm_members", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.Sticker", 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("Image") - .HasColumnType("jsonb") - .HasColumnName("image"); - - b.Property("ImageId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("image_id"); - - b.Property("PackId") - .HasColumnType("uuid") - .HasColumnName("pack_id"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_stickers"); - - b.HasIndex("PackId") - .HasDatabaseName("ix_stickers_pack_id"); - - b.HasIndex("Slug") - .HasDatabaseName("ix_stickers_slug"); - - b.ToTable("stickers", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.StickerPack", 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("Description") - .IsRequired() - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("Prefix") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("prefix"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_sticker_packs"); - - b.HasIndex("Prefix") - .IsUnique() - .HasDatabaseName("ix_sticker_packs_prefix"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_sticker_packs_publisher_id"); - - b.ToTable("sticker_packs", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFile", b => - { - b.Property("Id") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .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("Description") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property>("FileMeta") - .HasColumnType("jsonb") - .HasColumnName("file_meta"); - - b.Property("HasCompression") - .HasColumnType("boolean") - .HasColumnName("has_compression"); - - b.Property("Hash") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("hash"); - - b.Property("MessageId") - .HasColumnType("uuid") - .HasColumnName("message_id"); - - b.Property("MimeType") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("mime_type"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("PostId") - .HasColumnType("uuid") - .HasColumnName("post_id"); - - b.Property>("SensitiveMarks") - .HasColumnType("jsonb") - .HasColumnName("sensitive_marks"); - - b.Property("Size") - .HasColumnType("bigint") - .HasColumnName("size"); - - b.Property("StorageId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("storage_id"); - - b.Property("StorageUrl") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("storage_url"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("UploadedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("uploaded_at"); - - b.Property("UploadedTo") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("uploaded_to"); - - b.Property>("UserMeta") - .HasColumnType("jsonb") - .HasColumnName("user_meta"); - - b.HasKey("Id") - .HasName("pk_files"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_files_account_id"); - - b.HasIndex("MessageId") - .HasDatabaseName("ix_files_message_id"); - - b.HasIndex("PostId") - .HasDatabaseName("ix_files_post_id"); - - b.ToTable("files", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFileReference", 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("ExpiredAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expired_at"); - - b.Property("FileId") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("file_id"); - - b.Property("ResourceId") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("resource_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("Usage") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("usage"); - - b.HasKey("Id") - .HasName("pk_file_references"); - - b.HasIndex("FileId") - .HasDatabaseName("ix_file_references_file_id"); - - b.ToTable("file_references", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Order", 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("ExpiredAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expired_at"); - - b.Property("IssuerAppId") - .HasColumnType("uuid") - .HasColumnName("issuer_app_id"); - - b.Property("PayeeWalletId") - .HasColumnType("uuid") - .HasColumnName("payee_wallet_id"); - - 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("IssuerAppId") - .HasDatabaseName("ix_payment_orders_issuer_app_id"); - - 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.Sphere.Wallet.Transaction", 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.Sphere.Wallet.Wallet", 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.Sphere.Wallet.WalletPocket", 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("PostPostCategory", b => - { - b.Property("CategoriesId") - .HasColumnType("uuid") - .HasColumnName("categories_id"); - - b.Property("PostsId") - .HasColumnType("uuid") - .HasColumnName("posts_id"); - - b.HasKey("CategoriesId", "PostsId") - .HasName("pk_post_category_links"); - - b.HasIndex("PostsId") - .HasDatabaseName("ix_post_category_links_posts_id"); - - b.ToTable("post_category_links", (string)null); - }); - - modelBuilder.Entity("PostPostCollection", b => - { - b.Property("CollectionsId") - .HasColumnType("uuid") - .HasColumnName("collections_id"); - - b.Property("PostsId") - .HasColumnType("uuid") - .HasColumnName("posts_id"); - - b.HasKey("CollectionsId", "PostsId") - .HasName("pk_post_collection_links"); - - b.HasIndex("PostsId") - .HasDatabaseName("ix_post_collection_links_posts_id"); - - b.ToTable("post_collection_links", (string)null); - }); - - modelBuilder.Entity("PostPostTag", b => - { - b.Property("PostsId") - .HasColumnType("uuid") - .HasColumnName("posts_id"); - - b.Property("TagsId") - .HasColumnType("uuid") - .HasColumnName("tags_id"); - - b.HasKey("PostsId", "TagsId") - .HasName("pk_post_tag_links"); - - b.HasIndex("TagsId") - .HasDatabaseName("ix_post_tag_links_tags_id"); - - b.ToTable("post_tag_links", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.AccountAuthFactor", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("AuthFactors") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_auth_factors_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.AccountContact", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Contacts") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_contacts_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.ActionLog", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_action_logs_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Auth.Session", "Session") - .WithMany() - .HasForeignKey("SessionId") - .HasConstraintName("fk_action_logs_auth_sessions_session_id"); - - b.Navigation("Account"); - - b.Navigation("Session"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Badge", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Badges") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_badges_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.CheckInResult", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_check_in_results_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.MagicSpell", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .HasConstraintName("fk_magic_spells_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Notification", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_notifications_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.NotificationPushSubscription", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_notification_push_subscriptions_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Profile", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithOne("Profile") - .HasForeignKey("DysonNetwork.Sphere.Account.Profile", "AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_profiles_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Relationship", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("OutgoingRelationships") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_relationships_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Account.Account", "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.Sphere.Account.Status", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_statuses_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Activity.Activity", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_activities_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Challenge", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Challenges") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_challenges_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Session", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Sessions") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_sessions_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Auth.Challenge", "Challenge") - .WithMany() - .HasForeignKey("ChallengeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_sessions_auth_challenges_challenge_id"); - - b.Navigation("Account"); - - b.Navigation("Challenge"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatMember", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_members_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatRoom", "ChatRoom") - .WithMany("Members") - .HasForeignKey("ChatRoomId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_members_chat_rooms_chat_room_id"); - - b.Navigation("Account"); - - b.Navigation("ChatRoom"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatRoom", b => - { - b.HasOne("DysonNetwork.Sphere.Realm.Realm", "Realm") - .WithMany("ChatRooms") - .HasForeignKey("RealmId") - .HasConstraintName("fk_chat_rooms_realms_realm_id"); - - b.Navigation("Realm"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.Message", b => - { - b.HasOne("DysonNetwork.Sphere.Chat.ChatRoom", "ChatRoom") - .WithMany() - .HasForeignKey("ChatRoomId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_messages_chat_rooms_chat_room_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.Message", "ForwardedMessage") - .WithMany() - .HasForeignKey("ForwardedMessageId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_chat_messages_chat_messages_forwarded_message_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.Message", "RepliedMessage") - .WithMany() - .HasForeignKey("RepliedMessageId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_chat_messages_chat_messages_replied_message_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatMember", "Sender") - .WithMany() - .HasForeignKey("SenderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_messages_chat_members_sender_id"); - - b.Navigation("ChatRoom"); - - b.Navigation("ForwardedMessage"); - - b.Navigation("RepliedMessage"); - - b.Navigation("Sender"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.MessageReaction", b => - { - b.HasOne("DysonNetwork.Sphere.Chat.Message", "Message") - .WithMany("Reactions") - .HasForeignKey("MessageId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_reactions_chat_messages_message_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatMember", "Sender") - .WithMany() - .HasForeignKey("SenderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_reactions_chat_members_sender_id"); - - b.Navigation("Message"); - - b.Navigation("Sender"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.RealtimeCall", b => - { - b.HasOne("DysonNetwork.Sphere.Chat.ChatRoom", "Room") - .WithMany() - .HasForeignKey("RoomId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_realtime_call_chat_rooms_room_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatMember", "Sender") - .WithMany() - .HasForeignKey("SenderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_realtime_call_chat_members_sender_id"); - - b.Navigation("Room"); - - b.Navigation("Sender"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomApp", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Developer") - .WithMany() - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_custom_apps_publishers_publisher_id"); - - b.Navigation("Developer"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomAppSecret", b => - { - b.HasOne("DysonNetwork.Sphere.Developer.CustomApp", "App") - .WithMany() - .HasForeignKey("AppId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_custom_app_secrets_custom_apps_app_id"); - - b.Navigation("App"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroupMember", b => - { - b.HasOne("DysonNetwork.Sphere.Permission.PermissionGroup", "Group") - .WithMany("Members") - .HasForeignKey("GroupId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_permission_group_members_permission_groups_group_id"); - - b.Navigation("Group"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionNode", b => - { - b.HasOne("DysonNetwork.Sphere.Permission.PermissionGroup", "Group") - .WithMany("Nodes") - .HasForeignKey("GroupId") - .HasConstraintName("fk_permission_nodes_permission_groups_group_id"); - - b.Navigation("Group"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.Post", b => - { - b.HasOne("DysonNetwork.Sphere.Post.Post", "ForwardedPost") - .WithMany() - .HasForeignKey("ForwardedPostId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_posts_posts_forwarded_post_id"); - - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Posts") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_posts_publishers_publisher_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", "RepliedPost") - .WithMany() - .HasForeignKey("RepliedPostId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_posts_posts_replied_post_id"); - - b.Navigation("ForwardedPost"); - - b.Navigation("Publisher"); - - b.Navigation("RepliedPost"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostCollection", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Collections") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_collections_publishers_publisher_id"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostReaction", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_reactions_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", "Post") - .WithMany("Reactions") - .HasForeignKey("PostId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_reactions_posts_post_id"); - - b.Navigation("Account"); - - b.Navigation("Post"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.Publisher", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .HasConstraintName("fk_publishers_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Realm.Realm", "Realm") - .WithMany() - .HasForeignKey("RealmId") - .HasConstraintName("fk_publishers_realms_realm_id"); - - b.Navigation("Account"); - - b.Navigation("Realm"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherFeature", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany() - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_features_publishers_publisher_id"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherMember", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_members_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Members") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_members_publishers_publisher_id"); - - b.Navigation("Account"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherSubscription", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_subscriptions_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Subscriptions") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_subscriptions_publishers_publisher_id"); - - b.Navigation("Account"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.Realm", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_realms_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.RealmMember", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_realm_members_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Realm.Realm", "Realm") - .WithMany("Members") - .HasForeignKey("RealmId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_realm_members_realms_realm_id"); - - b.Navigation("Account"); - - b.Navigation("Realm"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.Sticker", b => - { - b.HasOne("DysonNetwork.Sphere.Sticker.StickerPack", "Pack") - .WithMany() - .HasForeignKey("PackId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_stickers_sticker_packs_pack_id"); - - b.Navigation("Pack"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.StickerPack", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany() - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_sticker_packs_publishers_publisher_id"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFile", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_files_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.Message", null) - .WithMany("OutdatedAttachments") - .HasForeignKey("MessageId") - .HasConstraintName("fk_files_chat_messages_message_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany("OutdatedAttachments") - .HasForeignKey("PostId") - .HasConstraintName("fk_files_posts_post_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFileReference", b => - { - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "File") - .WithMany() - .HasForeignKey("FileId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_file_references_files_file_id"); - - b.Navigation("File"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Order", b => - { - b.HasOne("DysonNetwork.Sphere.Developer.CustomApp", "IssuerApp") - .WithMany() - .HasForeignKey("IssuerAppId") - .HasConstraintName("fk_payment_orders_custom_apps_issuer_app_id"); - - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "PayeeWallet") - .WithMany() - .HasForeignKey("PayeeWalletId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_payment_orders_wallets_payee_wallet_id"); - - b.HasOne("DysonNetwork.Sphere.Wallet.Transaction", "Transaction") - .WithMany() - .HasForeignKey("TransactionId") - .HasConstraintName("fk_payment_orders_payment_transactions_transaction_id"); - - b.Navigation("IssuerApp"); - - b.Navigation("PayeeWallet"); - - b.Navigation("Transaction"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Transaction", b => - { - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "PayeeWallet") - .WithMany() - .HasForeignKey("PayeeWalletId") - .HasConstraintName("fk_payment_transactions_wallets_payee_wallet_id"); - - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "PayerWallet") - .WithMany() - .HasForeignKey("PayerWalletId") - .HasConstraintName("fk_payment_transactions_wallets_payer_wallet_id"); - - b.Navigation("PayeeWallet"); - - b.Navigation("PayerWallet"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Wallet", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_wallets_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.WalletPocket", b => - { - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "Wallet") - .WithMany("Pockets") - .HasForeignKey("WalletId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_wallet_pockets_wallets_wallet_id"); - - b.Navigation("Wallet"); - }); - - modelBuilder.Entity("PostPostCategory", b => - { - b.HasOne("DysonNetwork.Sphere.Post.PostCategory", null) - .WithMany() - .HasForeignKey("CategoriesId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_category_links_post_categories_categories_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany() - .HasForeignKey("PostsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_category_links_posts_posts_id"); - }); - - modelBuilder.Entity("PostPostCollection", b => - { - b.HasOne("DysonNetwork.Sphere.Post.PostCollection", null) - .WithMany() - .HasForeignKey("CollectionsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_collection_links_post_collections_collections_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany() - .HasForeignKey("PostsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_collection_links_posts_posts_id"); - }); - - modelBuilder.Entity("PostPostTag", b => - { - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany() - .HasForeignKey("PostsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_tag_links_posts_posts_id"); - - b.HasOne("DysonNetwork.Sphere.Post.PostTag", null) - .WithMany() - .HasForeignKey("TagsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_tag_links_post_tags_tags_id"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Account", b => - { - b.Navigation("AuthFactors"); - - b.Navigation("Badges"); - - b.Navigation("Challenges"); - - b.Navigation("Contacts"); - - b.Navigation("IncomingRelationships"); - - b.Navigation("OutgoingRelationships"); - - b.Navigation("Profile") - .IsRequired(); - - b.Navigation("Sessions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatRoom", b => - { - b.Navigation("Members"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.Message", b => - { - b.Navigation("OutdatedAttachments"); - - b.Navigation("Reactions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroup", b => - { - b.Navigation("Members"); - - b.Navigation("Nodes"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.Post", b => - { - b.Navigation("OutdatedAttachments"); - - b.Navigation("Reactions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.Publisher", b => - { - b.Navigation("Collections"); - - b.Navigation("Members"); - - b.Navigation("Posts"); - - b.Navigation("Subscriptions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.Realm", b => - { - b.Navigation("ChatRooms"); - - b.Navigation("Members"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Wallet", b => - { - b.Navigation("Pockets"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/DysonNetwork.Sphere/Migrations/20250603153937_BetterAuthFactor.cs b/DysonNetwork.Sphere/Migrations/20250603153937_BetterAuthFactor.cs deleted file mode 100644 index 2f95a5c..0000000 --- a/DysonNetwork.Sphere/Migrations/20250603153937_BetterAuthFactor.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System.Collections.Generic; -using Microsoft.EntityFrameworkCore.Migrations; -using NodaTime; - -#nullable disable - -namespace DysonNetwork.Sphere.Migrations -{ - /// - public partial class BetterAuthFactor : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn>( - name: "config", - table: "account_auth_factors", - type: "jsonb", - nullable: true); - - migrationBuilder.AddColumn( - name: "enabled_at", - table: "account_auth_factors", - type: "timestamp with time zone", - nullable: true); - - migrationBuilder.AddColumn( - name: "expired_at", - table: "account_auth_factors", - type: "timestamp with time zone", - nullable: true); - - migrationBuilder.AddColumn( - name: "trustworthy", - table: "account_auth_factors", - type: "integer", - nullable: false, - defaultValue: 0); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "config", - table: "account_auth_factors"); - - migrationBuilder.DropColumn( - name: "enabled_at", - table: "account_auth_factors"); - - migrationBuilder.DropColumn( - name: "expired_at", - table: "account_auth_factors"); - - migrationBuilder.DropColumn( - name: "trustworthy", - table: "account_auth_factors"); - } - } -} diff --git a/DysonNetwork.Sphere/Migrations/20250608114100_AccountContactCanBePrimary.Designer.cs b/DysonNetwork.Sphere/Migrations/20250608114100_AccountContactCanBePrimary.Designer.cs deleted file mode 100644 index 7f554cd..0000000 --- a/DysonNetwork.Sphere/Migrations/20250608114100_AccountContactCanBePrimary.Designer.cs +++ /dev/null @@ -1,3414 +0,0 @@ -// -using System; -using System.Collections.Generic; -using System.Text.Json; -using DysonNetwork.Sphere; -using DysonNetwork.Sphere.Account; -using DysonNetwork.Sphere.Storage; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using NetTopologySuite.Geometries; -using NodaTime; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; -using NpgsqlTypes; - -#nullable disable - -namespace DysonNetwork.Sphere.Migrations -{ - [DbContext(typeof(AppDatabase))] - [Migration("20250608114100_AccountContactCanBePrimary")] - partial class AccountContactCanBePrimary - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "9.0.3") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "postgis"); - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Account", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("ActivatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("activated_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("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("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.Sphere.Account.AccountAuthFactor", 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.Sphere.Account.AccountContact", 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("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.Sphere.Account.ActionLog", 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("geometry") - .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.HasIndex("SessionId") - .HasDatabaseName("ix_action_logs_session_id"); - - b.ToTable("action_logs", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Badge", 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("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.Sphere.Account.CheckInResult", 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("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.Sphere.Account.MagicSpell", 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.Sphere.Account.Notification", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("Content") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .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>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("Priority") - .HasColumnType("integer") - .HasColumnName("priority"); - - b.Property("Subtitle") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("subtitle"); - - b.Property("Title") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("title"); - - b.Property("Topic") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("topic"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("ViewedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("viewed_at"); - - b.HasKey("Id") - .HasName("pk_notifications"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_notifications_account_id"); - - b.ToTable("notifications", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.NotificationPushSubscription", 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(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("device_id"); - - b.Property("DeviceToken") - .IsRequired() - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("device_token"); - - b.Property("LastUsedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_used_at"); - - b.Property("Provider") - .HasColumnType("integer") - .HasColumnName("provider"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_notification_push_subscriptions"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_notification_push_subscriptions_account_id"); - - b.HasIndex("DeviceToken", "DeviceId", "AccountId") - .IsUnique() - .HasDatabaseName("ix_notification_push_subscriptions_device_token_device_id_acco"); - - b.ToTable("notification_push_subscriptions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Profile", 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("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_id"); - - 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("MiddleName") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("middle_name"); - - b.Property("Picture") - .HasColumnType("jsonb") - .HasColumnName("picture"); - - b.Property("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - b.Property("Pronouns") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("pronouns"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - 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.Sphere.Account.Relationship", 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.Sphere.Account.Status", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - 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("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("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.Sphere.Activity.Activity", 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>("Meta") - .IsRequired() - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("ResourceIdentifier") - .IsRequired() - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("resource_identifier"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property>("UsersVisible") - .IsRequired() - .HasColumnType("jsonb") - .HasColumnName("users_visible"); - - b.Property("Visibility") - .HasColumnType("integer") - .HasColumnName("visibility"); - - b.HasKey("Id") - .HasName("pk_activities"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_activities_account_id"); - - b.ToTable("activities", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Challenge", 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") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("device_id"); - - 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("geometry") - .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("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_challenges"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_auth_challenges_account_id"); - - b.ToTable("auth_challenges", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Session", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("ChallengeId") - .HasColumnType("uuid") - .HasColumnName("challenge_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("Label") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("label"); - - b.Property("LastGrantedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_granted_at"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - 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.ToTable("auth_sessions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatMember", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("ChatRoomId") - .HasColumnType("uuid") - .HasColumnName("chat_room_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("IsBot") - .HasColumnType("boolean") - .HasColumnName("is_bot"); - - b.Property("JoinedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("joined_at"); - - b.Property("LastReadAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_read_at"); - - b.Property("LeaveAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("leave_at"); - - b.Property("Nick") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("nick"); - - b.Property("Notify") - .HasColumnType("integer") - .HasColumnName("notify"); - - b.Property("Role") - .HasColumnType("integer") - .HasColumnName("role"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_chat_members"); - - b.HasAlternateKey("ChatRoomId", "AccountId") - .HasName("ak_chat_members_chat_room_id_account_id"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_chat_members_account_id"); - - b.ToTable("chat_members", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatRoom", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("Background") - .HasColumnType("jsonb") - .HasColumnName("background"); - - b.Property("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_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("Description") - .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") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("Picture") - .HasColumnType("jsonb") - .HasColumnName("picture"); - - b.Property("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - b.Property("RealmId") - .HasColumnType("uuid") - .HasColumnName("realm_id"); - - b.Property("Type") - .HasColumnType("integer") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_chat_rooms"); - - b.HasIndex("RealmId") - .HasDatabaseName("ix_chat_rooms_realm_id"); - - b.ToTable("chat_rooms", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.Message", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property>("Attachments") - .IsRequired() - .HasColumnType("jsonb") - .HasColumnName("attachments"); - - b.Property("ChatRoomId") - .HasColumnType("uuid") - .HasColumnName("chat_room_id"); - - b.Property("Content") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .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("EditedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("edited_at"); - - b.Property("ForwardedMessageId") - .HasColumnType("uuid") - .HasColumnName("forwarded_message_id"); - - b.Property>("MembersMentioned") - .HasColumnType("jsonb") - .HasColumnName("members_mentioned"); - - b.Property>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("Nonce") - .IsRequired() - .HasMaxLength(36) - .HasColumnType("character varying(36)") - .HasColumnName("nonce"); - - b.Property("RepliedMessageId") - .HasColumnType("uuid") - .HasColumnName("replied_message_id"); - - b.Property("SenderId") - .HasColumnType("uuid") - .HasColumnName("sender_id"); - - 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_chat_messages"); - - b.HasIndex("ChatRoomId") - .HasDatabaseName("ix_chat_messages_chat_room_id"); - - b.HasIndex("ForwardedMessageId") - .HasDatabaseName("ix_chat_messages_forwarded_message_id"); - - b.HasIndex("RepliedMessageId") - .HasDatabaseName("ix_chat_messages_replied_message_id"); - - b.HasIndex("SenderId") - .HasDatabaseName("ix_chat_messages_sender_id"); - - b.ToTable("chat_messages", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.MessageReaction", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("Attitude") - .HasColumnType("integer") - .HasColumnName("attitude"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("MessageId") - .HasColumnType("uuid") - .HasColumnName("message_id"); - - b.Property("SenderId") - .HasColumnType("uuid") - .HasColumnName("sender_id"); - - b.Property("Symbol") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("symbol"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_chat_reactions"); - - b.HasIndex("MessageId") - .HasDatabaseName("ix_chat_reactions_message_id"); - - b.HasIndex("SenderId") - .HasDatabaseName("ix_chat_reactions_sender_id"); - - b.ToTable("chat_reactions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.RealtimeCall", 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("EndedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("ended_at"); - - b.Property("ProviderName") - .HasColumnType("text") - .HasColumnName("provider_name"); - - b.Property("RoomId") - .HasColumnType("uuid") - .HasColumnName("room_id"); - - b.Property("SenderId") - .HasColumnType("uuid") - .HasColumnName("sender_id"); - - b.Property("SessionId") - .HasColumnType("text") - .HasColumnName("session_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("UpstreamConfigJson") - .HasColumnType("jsonb") - .HasColumnName("upstream"); - - b.HasKey("Id") - .HasName("pk_chat_realtime_call"); - - b.HasIndex("RoomId") - .HasDatabaseName("ix_chat_realtime_call_room_id"); - - b.HasIndex("SenderId") - .HasDatabaseName("ix_chat_realtime_call_sender_id"); - - b.ToTable("chat_realtime_call", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomApp", 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("Name") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("slug"); - - b.Property("Status") - .HasColumnType("integer") - .HasColumnName("status"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("VerifiedAs") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("verified_as"); - - b.Property("VerifiedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("verified_at"); - - b.HasKey("Id") - .HasName("pk_custom_apps"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_custom_apps_publisher_id"); - - b.ToTable("custom_apps", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomAppSecret", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AppId") - .HasColumnType("uuid") - .HasColumnName("app_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("Remarks") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("remarks"); - - b.Property("Secret") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("secret"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_custom_app_secrets"); - - b.HasIndex("AppId") - .HasDatabaseName("ix_custom_app_secrets_app_id"); - - b.ToTable("custom_app_secrets", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroup", 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.Sphere.Permission.PermissionGroupMember", 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.Sphere.Permission.PermissionNode", 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("Area") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("area"); - - 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("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", "Area", "Actor") - .HasDatabaseName("ix_permission_nodes_key_area_actor"); - - b.ToTable("permission_nodes", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.Post", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property>("Attachments") - .IsRequired() - .HasColumnType("jsonb") - .HasColumnName("attachments"); - - b.Property("Content") - .HasColumnType("text") - .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("Description") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("Downvotes") - .HasColumnType("integer") - .HasColumnName("downvotes"); - - b.Property("EditedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("edited_at"); - - b.Property("ForwardedPostId") - .HasColumnType("uuid") - .HasColumnName("forwarded_post_id"); - - b.Property("Language") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("language"); - - b.Property>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("PublishedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("published_at"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("RepliedPostId") - .HasColumnType("uuid") - .HasColumnName("replied_post_id"); - - b.Property("SearchVector") - .IsRequired() - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("tsvector") - .HasColumnName("search_vector") - .HasAnnotation("Npgsql:TsVectorConfig", "simple") - .HasAnnotation("Npgsql:TsVectorProperties", new[] { "Title", "Description", "Content" }); - - b.Property("Title") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("title"); - - b.Property("Type") - .HasColumnType("integer") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("Upvotes") - .HasColumnType("integer") - .HasColumnName("upvotes"); - - b.Property("ViewsTotal") - .HasColumnType("integer") - .HasColumnName("views_total"); - - b.Property("ViewsUnique") - .HasColumnType("integer") - .HasColumnName("views_unique"); - - b.Property("Visibility") - .HasColumnType("integer") - .HasColumnName("visibility"); - - b.HasKey("Id") - .HasName("pk_posts"); - - b.HasIndex("ForwardedPostId") - .HasDatabaseName("ix_posts_forwarded_post_id"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_posts_publisher_id"); - - b.HasIndex("RepliedPostId") - .HasDatabaseName("ix_posts_replied_post_id"); - - b.HasIndex("SearchVector") - .HasDatabaseName("ix_posts_search_vector"); - - NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex("SearchVector"), "GIN"); - - b.ToTable("posts", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostCategory", 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("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("name"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_categories"); - - b.ToTable("post_categories", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostCollection", 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("Description") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("name"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_collections"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_post_collections_publisher_id"); - - b.ToTable("post_collections", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostReaction", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("Attitude") - .HasColumnType("integer") - .HasColumnName("attitude"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("PostId") - .HasColumnType("uuid") - .HasColumnName("post_id"); - - b.Property("Symbol") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("symbol"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_reactions"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_post_reactions_account_id"); - - b.HasIndex("PostId") - .HasDatabaseName("ix_post_reactions_post_id"); - - b.ToTable("post_reactions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostTag", 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("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("name"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_tags"); - - b.ToTable("post_tags", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.Publisher", 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("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_id"); - - b.Property("Bio") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("bio"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - 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("Picture") - .HasColumnType("jsonb") - .HasColumnName("picture"); - - b.Property("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - b.Property("RealmId") - .HasColumnType("uuid") - .HasColumnName("realm_id"); - - b.Property("Type") - .HasColumnType("integer") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_publishers"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_publishers_account_id"); - - b.HasIndex("Name") - .IsUnique() - .HasDatabaseName("ix_publishers_name"); - - b.HasIndex("RealmId") - .HasDatabaseName("ix_publishers_realm_id"); - - b.ToTable("publishers", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherFeature", 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("ExpiredAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expired_at"); - - b.Property("Flag") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("flag"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_publisher_features"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_publisher_features_publisher_id"); - - b.ToTable("publisher_features", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherMember", b => - { - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_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("Role") - .HasColumnType("integer") - .HasColumnName("role"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("PublisherId", "AccountId") - .HasName("pk_publisher_members"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_publisher_members_account_id"); - - b.ToTable("publisher_members", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherSubscription", 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("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("Status") - .HasColumnType("integer") - .HasColumnName("status"); - - b.Property("Tier") - .HasColumnType("integer") - .HasColumnName("tier"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_publisher_subscriptions"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_publisher_subscriptions_account_id"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_publisher_subscriptions_publisher_id"); - - b.ToTable("publisher_subscriptions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.Realm", 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("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_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("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("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - 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("VerifiedAs") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("verified_as"); - - b.Property("VerifiedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("verified_at"); - - b.HasKey("Id") - .HasName("pk_realms"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_realms_account_id"); - - b.HasIndex("Slug") - .IsUnique() - .HasDatabaseName("ix_realms_slug"); - - b.ToTable("realms", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.RealmMember", 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.HasIndex("AccountId") - .HasDatabaseName("ix_realm_members_account_id"); - - b.ToTable("realm_members", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.Sticker", 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("Image") - .HasColumnType("jsonb") - .HasColumnName("image"); - - b.Property("ImageId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("image_id"); - - b.Property("PackId") - .HasColumnType("uuid") - .HasColumnName("pack_id"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_stickers"); - - b.HasIndex("PackId") - .HasDatabaseName("ix_stickers_pack_id"); - - b.HasIndex("Slug") - .HasDatabaseName("ix_stickers_slug"); - - b.ToTable("stickers", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.StickerPack", 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("Description") - .IsRequired() - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("Prefix") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("prefix"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_sticker_packs"); - - b.HasIndex("Prefix") - .IsUnique() - .HasDatabaseName("ix_sticker_packs_prefix"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_sticker_packs_publisher_id"); - - b.ToTable("sticker_packs", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFile", b => - { - b.Property("Id") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .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("Description") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property>("FileMeta") - .HasColumnType("jsonb") - .HasColumnName("file_meta"); - - b.Property("HasCompression") - .HasColumnType("boolean") - .HasColumnName("has_compression"); - - b.Property("Hash") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("hash"); - - b.Property("MessageId") - .HasColumnType("uuid") - .HasColumnName("message_id"); - - b.Property("MimeType") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("mime_type"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("PostId") - .HasColumnType("uuid") - .HasColumnName("post_id"); - - b.Property>("SensitiveMarks") - .HasColumnType("jsonb") - .HasColumnName("sensitive_marks"); - - b.Property("Size") - .HasColumnType("bigint") - .HasColumnName("size"); - - b.Property("StorageId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("storage_id"); - - b.Property("StorageUrl") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("storage_url"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("UploadedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("uploaded_at"); - - b.Property("UploadedTo") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("uploaded_to"); - - b.Property>("UserMeta") - .HasColumnType("jsonb") - .HasColumnName("user_meta"); - - b.HasKey("Id") - .HasName("pk_files"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_files_account_id"); - - b.HasIndex("MessageId") - .HasDatabaseName("ix_files_message_id"); - - b.HasIndex("PostId") - .HasDatabaseName("ix_files_post_id"); - - b.ToTable("files", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFileReference", 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("ExpiredAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expired_at"); - - b.Property("FileId") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("file_id"); - - b.Property("ResourceId") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("resource_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("Usage") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("usage"); - - b.HasKey("Id") - .HasName("pk_file_references"); - - b.HasIndex("FileId") - .HasDatabaseName("ix_file_references_file_id"); - - b.ToTable("file_references", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Order", 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("ExpiredAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expired_at"); - - b.Property("IssuerAppId") - .HasColumnType("uuid") - .HasColumnName("issuer_app_id"); - - b.Property("PayeeWalletId") - .HasColumnType("uuid") - .HasColumnName("payee_wallet_id"); - - 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("IssuerAppId") - .HasDatabaseName("ix_payment_orders_issuer_app_id"); - - 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.Sphere.Wallet.Transaction", 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.Sphere.Wallet.Wallet", 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.Sphere.Wallet.WalletPocket", 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("PostPostCategory", b => - { - b.Property("CategoriesId") - .HasColumnType("uuid") - .HasColumnName("categories_id"); - - b.Property("PostsId") - .HasColumnType("uuid") - .HasColumnName("posts_id"); - - b.HasKey("CategoriesId", "PostsId") - .HasName("pk_post_category_links"); - - b.HasIndex("PostsId") - .HasDatabaseName("ix_post_category_links_posts_id"); - - b.ToTable("post_category_links", (string)null); - }); - - modelBuilder.Entity("PostPostCollection", b => - { - b.Property("CollectionsId") - .HasColumnType("uuid") - .HasColumnName("collections_id"); - - b.Property("PostsId") - .HasColumnType("uuid") - .HasColumnName("posts_id"); - - b.HasKey("CollectionsId", "PostsId") - .HasName("pk_post_collection_links"); - - b.HasIndex("PostsId") - .HasDatabaseName("ix_post_collection_links_posts_id"); - - b.ToTable("post_collection_links", (string)null); - }); - - modelBuilder.Entity("PostPostTag", b => - { - b.Property("PostsId") - .HasColumnType("uuid") - .HasColumnName("posts_id"); - - b.Property("TagsId") - .HasColumnType("uuid") - .HasColumnName("tags_id"); - - b.HasKey("PostsId", "TagsId") - .HasName("pk_post_tag_links"); - - b.HasIndex("TagsId") - .HasDatabaseName("ix_post_tag_links_tags_id"); - - b.ToTable("post_tag_links", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.AccountAuthFactor", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("AuthFactors") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_auth_factors_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.AccountContact", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Contacts") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_contacts_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.ActionLog", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_action_logs_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Auth.Session", "Session") - .WithMany() - .HasForeignKey("SessionId") - .HasConstraintName("fk_action_logs_auth_sessions_session_id"); - - b.Navigation("Account"); - - b.Navigation("Session"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Badge", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Badges") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_badges_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.CheckInResult", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_check_in_results_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.MagicSpell", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .HasConstraintName("fk_magic_spells_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Notification", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_notifications_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.NotificationPushSubscription", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_notification_push_subscriptions_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Profile", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithOne("Profile") - .HasForeignKey("DysonNetwork.Sphere.Account.Profile", "AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_profiles_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Relationship", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("OutgoingRelationships") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_relationships_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Account.Account", "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.Sphere.Account.Status", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_statuses_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Activity.Activity", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_activities_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Challenge", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Challenges") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_challenges_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Session", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Sessions") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_sessions_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Auth.Challenge", "Challenge") - .WithMany() - .HasForeignKey("ChallengeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_sessions_auth_challenges_challenge_id"); - - b.Navigation("Account"); - - b.Navigation("Challenge"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatMember", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_members_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatRoom", "ChatRoom") - .WithMany("Members") - .HasForeignKey("ChatRoomId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_members_chat_rooms_chat_room_id"); - - b.Navigation("Account"); - - b.Navigation("ChatRoom"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatRoom", b => - { - b.HasOne("DysonNetwork.Sphere.Realm.Realm", "Realm") - .WithMany("ChatRooms") - .HasForeignKey("RealmId") - .HasConstraintName("fk_chat_rooms_realms_realm_id"); - - b.Navigation("Realm"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.Message", b => - { - b.HasOne("DysonNetwork.Sphere.Chat.ChatRoom", "ChatRoom") - .WithMany() - .HasForeignKey("ChatRoomId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_messages_chat_rooms_chat_room_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.Message", "ForwardedMessage") - .WithMany() - .HasForeignKey("ForwardedMessageId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_chat_messages_chat_messages_forwarded_message_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.Message", "RepliedMessage") - .WithMany() - .HasForeignKey("RepliedMessageId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_chat_messages_chat_messages_replied_message_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatMember", "Sender") - .WithMany() - .HasForeignKey("SenderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_messages_chat_members_sender_id"); - - b.Navigation("ChatRoom"); - - b.Navigation("ForwardedMessage"); - - b.Navigation("RepliedMessage"); - - b.Navigation("Sender"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.MessageReaction", b => - { - b.HasOne("DysonNetwork.Sphere.Chat.Message", "Message") - .WithMany("Reactions") - .HasForeignKey("MessageId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_reactions_chat_messages_message_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatMember", "Sender") - .WithMany() - .HasForeignKey("SenderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_reactions_chat_members_sender_id"); - - b.Navigation("Message"); - - b.Navigation("Sender"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.RealtimeCall", b => - { - b.HasOne("DysonNetwork.Sphere.Chat.ChatRoom", "Room") - .WithMany() - .HasForeignKey("RoomId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_realtime_call_chat_rooms_room_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatMember", "Sender") - .WithMany() - .HasForeignKey("SenderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_realtime_call_chat_members_sender_id"); - - b.Navigation("Room"); - - b.Navigation("Sender"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomApp", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Developer") - .WithMany() - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_custom_apps_publishers_publisher_id"); - - b.Navigation("Developer"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomAppSecret", b => - { - b.HasOne("DysonNetwork.Sphere.Developer.CustomApp", "App") - .WithMany() - .HasForeignKey("AppId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_custom_app_secrets_custom_apps_app_id"); - - b.Navigation("App"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroupMember", b => - { - b.HasOne("DysonNetwork.Sphere.Permission.PermissionGroup", "Group") - .WithMany("Members") - .HasForeignKey("GroupId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_permission_group_members_permission_groups_group_id"); - - b.Navigation("Group"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionNode", b => - { - b.HasOne("DysonNetwork.Sphere.Permission.PermissionGroup", "Group") - .WithMany("Nodes") - .HasForeignKey("GroupId") - .HasConstraintName("fk_permission_nodes_permission_groups_group_id"); - - b.Navigation("Group"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.Post", b => - { - b.HasOne("DysonNetwork.Sphere.Post.Post", "ForwardedPost") - .WithMany() - .HasForeignKey("ForwardedPostId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_posts_posts_forwarded_post_id"); - - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Posts") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_posts_publishers_publisher_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", "RepliedPost") - .WithMany() - .HasForeignKey("RepliedPostId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_posts_posts_replied_post_id"); - - b.Navigation("ForwardedPost"); - - b.Navigation("Publisher"); - - b.Navigation("RepliedPost"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostCollection", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Collections") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_collections_publishers_publisher_id"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostReaction", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_reactions_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", "Post") - .WithMany("Reactions") - .HasForeignKey("PostId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_reactions_posts_post_id"); - - b.Navigation("Account"); - - b.Navigation("Post"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.Publisher", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .HasConstraintName("fk_publishers_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Realm.Realm", "Realm") - .WithMany() - .HasForeignKey("RealmId") - .HasConstraintName("fk_publishers_realms_realm_id"); - - b.Navigation("Account"); - - b.Navigation("Realm"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherFeature", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany() - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_features_publishers_publisher_id"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherMember", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_members_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Members") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_members_publishers_publisher_id"); - - b.Navigation("Account"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherSubscription", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_subscriptions_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Subscriptions") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_subscriptions_publishers_publisher_id"); - - b.Navigation("Account"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.Realm", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_realms_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.RealmMember", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_realm_members_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Realm.Realm", "Realm") - .WithMany("Members") - .HasForeignKey("RealmId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_realm_members_realms_realm_id"); - - b.Navigation("Account"); - - b.Navigation("Realm"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.Sticker", b => - { - b.HasOne("DysonNetwork.Sphere.Sticker.StickerPack", "Pack") - .WithMany() - .HasForeignKey("PackId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_stickers_sticker_packs_pack_id"); - - b.Navigation("Pack"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.StickerPack", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany() - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_sticker_packs_publishers_publisher_id"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFile", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_files_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.Message", null) - .WithMany("OutdatedAttachments") - .HasForeignKey("MessageId") - .HasConstraintName("fk_files_chat_messages_message_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany("OutdatedAttachments") - .HasForeignKey("PostId") - .HasConstraintName("fk_files_posts_post_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFileReference", b => - { - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "File") - .WithMany() - .HasForeignKey("FileId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_file_references_files_file_id"); - - b.Navigation("File"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Order", b => - { - b.HasOne("DysonNetwork.Sphere.Developer.CustomApp", "IssuerApp") - .WithMany() - .HasForeignKey("IssuerAppId") - .HasConstraintName("fk_payment_orders_custom_apps_issuer_app_id"); - - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "PayeeWallet") - .WithMany() - .HasForeignKey("PayeeWalletId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_payment_orders_wallets_payee_wallet_id"); - - b.HasOne("DysonNetwork.Sphere.Wallet.Transaction", "Transaction") - .WithMany() - .HasForeignKey("TransactionId") - .HasConstraintName("fk_payment_orders_payment_transactions_transaction_id"); - - b.Navigation("IssuerApp"); - - b.Navigation("PayeeWallet"); - - b.Navigation("Transaction"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Transaction", b => - { - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "PayeeWallet") - .WithMany() - .HasForeignKey("PayeeWalletId") - .HasConstraintName("fk_payment_transactions_wallets_payee_wallet_id"); - - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "PayerWallet") - .WithMany() - .HasForeignKey("PayerWalletId") - .HasConstraintName("fk_payment_transactions_wallets_payer_wallet_id"); - - b.Navigation("PayeeWallet"); - - b.Navigation("PayerWallet"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Wallet", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_wallets_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.WalletPocket", b => - { - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "Wallet") - .WithMany("Pockets") - .HasForeignKey("WalletId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_wallet_pockets_wallets_wallet_id"); - - b.Navigation("Wallet"); - }); - - modelBuilder.Entity("PostPostCategory", b => - { - b.HasOne("DysonNetwork.Sphere.Post.PostCategory", null) - .WithMany() - .HasForeignKey("CategoriesId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_category_links_post_categories_categories_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany() - .HasForeignKey("PostsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_category_links_posts_posts_id"); - }); - - modelBuilder.Entity("PostPostCollection", b => - { - b.HasOne("DysonNetwork.Sphere.Post.PostCollection", null) - .WithMany() - .HasForeignKey("CollectionsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_collection_links_post_collections_collections_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany() - .HasForeignKey("PostsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_collection_links_posts_posts_id"); - }); - - modelBuilder.Entity("PostPostTag", b => - { - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany() - .HasForeignKey("PostsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_tag_links_posts_posts_id"); - - b.HasOne("DysonNetwork.Sphere.Post.PostTag", null) - .WithMany() - .HasForeignKey("TagsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_tag_links_post_tags_tags_id"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Account", b => - { - b.Navigation("AuthFactors"); - - b.Navigation("Badges"); - - b.Navigation("Challenges"); - - b.Navigation("Contacts"); - - b.Navigation("IncomingRelationships"); - - b.Navigation("OutgoingRelationships"); - - b.Navigation("Profile") - .IsRequired(); - - b.Navigation("Sessions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatRoom", b => - { - b.Navigation("Members"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.Message", b => - { - b.Navigation("OutdatedAttachments"); - - b.Navigation("Reactions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroup", b => - { - b.Navigation("Members"); - - b.Navigation("Nodes"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.Post", b => - { - b.Navigation("OutdatedAttachments"); - - b.Navigation("Reactions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.Publisher", b => - { - b.Navigation("Collections"); - - b.Navigation("Members"); - - b.Navigation("Posts"); - - b.Navigation("Subscriptions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.Realm", b => - { - b.Navigation("ChatRooms"); - - b.Navigation("Members"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Wallet", b => - { - b.Navigation("Pockets"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/DysonNetwork.Sphere/Migrations/20250608114100_AccountContactCanBePrimary.cs b/DysonNetwork.Sphere/Migrations/20250608114100_AccountContactCanBePrimary.cs deleted file mode 100644 index 5506b05..0000000 --- a/DysonNetwork.Sphere/Migrations/20250608114100_AccountContactCanBePrimary.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace DysonNetwork.Sphere.Migrations -{ - /// - public partial class AccountContactCanBePrimary : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "is_primary", - table: "account_contacts", - type: "boolean", - nullable: false, - defaultValue: false); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "is_primary", - table: "account_contacts"); - } - } -} diff --git a/DysonNetwork.Sphere/Migrations/20250608152205_RemoveActivities.Designer.cs b/DysonNetwork.Sphere/Migrations/20250608152205_RemoveActivities.Designer.cs deleted file mode 100644 index 97da94d..0000000 --- a/DysonNetwork.Sphere/Migrations/20250608152205_RemoveActivities.Designer.cs +++ /dev/null @@ -1,3344 +0,0 @@ -// -using System; -using System.Collections.Generic; -using System.Text.Json; -using DysonNetwork.Sphere; -using DysonNetwork.Sphere.Account; -using DysonNetwork.Sphere.Storage; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using NetTopologySuite.Geometries; -using NodaTime; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; -using NpgsqlTypes; - -#nullable disable - -namespace DysonNetwork.Sphere.Migrations -{ - [DbContext(typeof(AppDatabase))] - [Migration("20250608152205_RemoveActivities")] - partial class RemoveActivities - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "9.0.3") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "postgis"); - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Account", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("ActivatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("activated_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("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("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.Sphere.Account.AccountAuthFactor", 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.Sphere.Account.AccountContact", 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("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.Sphere.Account.ActionLog", 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("geometry") - .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.HasIndex("SessionId") - .HasDatabaseName("ix_action_logs_session_id"); - - b.ToTable("action_logs", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Badge", 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("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.Sphere.Account.CheckInResult", 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("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.Sphere.Account.MagicSpell", 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.Sphere.Account.Notification", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("Content") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .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>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("Priority") - .HasColumnType("integer") - .HasColumnName("priority"); - - b.Property("Subtitle") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("subtitle"); - - b.Property("Title") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("title"); - - b.Property("Topic") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("topic"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("ViewedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("viewed_at"); - - b.HasKey("Id") - .HasName("pk_notifications"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_notifications_account_id"); - - b.ToTable("notifications", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.NotificationPushSubscription", 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(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("device_id"); - - b.Property("DeviceToken") - .IsRequired() - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("device_token"); - - b.Property("LastUsedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_used_at"); - - b.Property("Provider") - .HasColumnType("integer") - .HasColumnName("provider"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_notification_push_subscriptions"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_notification_push_subscriptions_account_id"); - - b.HasIndex("DeviceToken", "DeviceId", "AccountId") - .IsUnique() - .HasDatabaseName("ix_notification_push_subscriptions_device_token_device_id_acco"); - - b.ToTable("notification_push_subscriptions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Profile", 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("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_id"); - - 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("MiddleName") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("middle_name"); - - b.Property("Picture") - .HasColumnType("jsonb") - .HasColumnName("picture"); - - b.Property("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - b.Property("Pronouns") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("pronouns"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - 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.Sphere.Account.Relationship", 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.Sphere.Account.Status", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - 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("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("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.Sphere.Auth.Challenge", 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") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("device_id"); - - 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("geometry") - .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("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_challenges"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_auth_challenges_account_id"); - - b.ToTable("auth_challenges", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Session", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("ChallengeId") - .HasColumnType("uuid") - .HasColumnName("challenge_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("Label") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("label"); - - b.Property("LastGrantedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_granted_at"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - 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.ToTable("auth_sessions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatMember", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("ChatRoomId") - .HasColumnType("uuid") - .HasColumnName("chat_room_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("IsBot") - .HasColumnType("boolean") - .HasColumnName("is_bot"); - - b.Property("JoinedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("joined_at"); - - b.Property("LastReadAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_read_at"); - - b.Property("LeaveAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("leave_at"); - - b.Property("Nick") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("nick"); - - b.Property("Notify") - .HasColumnType("integer") - .HasColumnName("notify"); - - b.Property("Role") - .HasColumnType("integer") - .HasColumnName("role"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_chat_members"); - - b.HasAlternateKey("ChatRoomId", "AccountId") - .HasName("ak_chat_members_chat_room_id_account_id"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_chat_members_account_id"); - - b.ToTable("chat_members", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatRoom", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("Background") - .HasColumnType("jsonb") - .HasColumnName("background"); - - b.Property("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_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("Description") - .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") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("Picture") - .HasColumnType("jsonb") - .HasColumnName("picture"); - - b.Property("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - b.Property("RealmId") - .HasColumnType("uuid") - .HasColumnName("realm_id"); - - b.Property("Type") - .HasColumnType("integer") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_chat_rooms"); - - b.HasIndex("RealmId") - .HasDatabaseName("ix_chat_rooms_realm_id"); - - b.ToTable("chat_rooms", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.Message", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property>("Attachments") - .IsRequired() - .HasColumnType("jsonb") - .HasColumnName("attachments"); - - b.Property("ChatRoomId") - .HasColumnType("uuid") - .HasColumnName("chat_room_id"); - - b.Property("Content") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .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("EditedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("edited_at"); - - b.Property("ForwardedMessageId") - .HasColumnType("uuid") - .HasColumnName("forwarded_message_id"); - - b.Property>("MembersMentioned") - .HasColumnType("jsonb") - .HasColumnName("members_mentioned"); - - b.Property>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("Nonce") - .IsRequired() - .HasMaxLength(36) - .HasColumnType("character varying(36)") - .HasColumnName("nonce"); - - b.Property("RepliedMessageId") - .HasColumnType("uuid") - .HasColumnName("replied_message_id"); - - b.Property("SenderId") - .HasColumnType("uuid") - .HasColumnName("sender_id"); - - 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_chat_messages"); - - b.HasIndex("ChatRoomId") - .HasDatabaseName("ix_chat_messages_chat_room_id"); - - b.HasIndex("ForwardedMessageId") - .HasDatabaseName("ix_chat_messages_forwarded_message_id"); - - b.HasIndex("RepliedMessageId") - .HasDatabaseName("ix_chat_messages_replied_message_id"); - - b.HasIndex("SenderId") - .HasDatabaseName("ix_chat_messages_sender_id"); - - b.ToTable("chat_messages", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.MessageReaction", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("Attitude") - .HasColumnType("integer") - .HasColumnName("attitude"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("MessageId") - .HasColumnType("uuid") - .HasColumnName("message_id"); - - b.Property("SenderId") - .HasColumnType("uuid") - .HasColumnName("sender_id"); - - b.Property("Symbol") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("symbol"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_chat_reactions"); - - b.HasIndex("MessageId") - .HasDatabaseName("ix_chat_reactions_message_id"); - - b.HasIndex("SenderId") - .HasDatabaseName("ix_chat_reactions_sender_id"); - - b.ToTable("chat_reactions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.RealtimeCall", 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("EndedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("ended_at"); - - b.Property("ProviderName") - .HasColumnType("text") - .HasColumnName("provider_name"); - - b.Property("RoomId") - .HasColumnType("uuid") - .HasColumnName("room_id"); - - b.Property("SenderId") - .HasColumnType("uuid") - .HasColumnName("sender_id"); - - b.Property("SessionId") - .HasColumnType("text") - .HasColumnName("session_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("UpstreamConfigJson") - .HasColumnType("jsonb") - .HasColumnName("upstream"); - - b.HasKey("Id") - .HasName("pk_chat_realtime_call"); - - b.HasIndex("RoomId") - .HasDatabaseName("ix_chat_realtime_call_room_id"); - - b.HasIndex("SenderId") - .HasDatabaseName("ix_chat_realtime_call_sender_id"); - - b.ToTable("chat_realtime_call", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomApp", 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("Name") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("slug"); - - b.Property("Status") - .HasColumnType("integer") - .HasColumnName("status"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("VerifiedAs") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("verified_as"); - - b.Property("VerifiedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("verified_at"); - - b.HasKey("Id") - .HasName("pk_custom_apps"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_custom_apps_publisher_id"); - - b.ToTable("custom_apps", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomAppSecret", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AppId") - .HasColumnType("uuid") - .HasColumnName("app_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("Remarks") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("remarks"); - - b.Property("Secret") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("secret"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_custom_app_secrets"); - - b.HasIndex("AppId") - .HasDatabaseName("ix_custom_app_secrets_app_id"); - - b.ToTable("custom_app_secrets", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroup", 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.Sphere.Permission.PermissionGroupMember", 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.Sphere.Permission.PermissionNode", 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("Area") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("area"); - - 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("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", "Area", "Actor") - .HasDatabaseName("ix_permission_nodes_key_area_actor"); - - b.ToTable("permission_nodes", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.Post", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property>("Attachments") - .IsRequired() - .HasColumnType("jsonb") - .HasColumnName("attachments"); - - b.Property("Content") - .HasColumnType("text") - .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("Description") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("Downvotes") - .HasColumnType("integer") - .HasColumnName("downvotes"); - - b.Property("EditedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("edited_at"); - - b.Property("ForwardedPostId") - .HasColumnType("uuid") - .HasColumnName("forwarded_post_id"); - - b.Property("Language") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("language"); - - b.Property>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("PublishedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("published_at"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("RepliedPostId") - .HasColumnType("uuid") - .HasColumnName("replied_post_id"); - - b.Property("SearchVector") - .IsRequired() - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("tsvector") - .HasColumnName("search_vector") - .HasAnnotation("Npgsql:TsVectorConfig", "simple") - .HasAnnotation("Npgsql:TsVectorProperties", new[] { "Title", "Description", "Content" }); - - b.Property("Title") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("title"); - - b.Property("Type") - .HasColumnType("integer") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("Upvotes") - .HasColumnType("integer") - .HasColumnName("upvotes"); - - b.Property("ViewsTotal") - .HasColumnType("integer") - .HasColumnName("views_total"); - - b.Property("ViewsUnique") - .HasColumnType("integer") - .HasColumnName("views_unique"); - - b.Property("Visibility") - .HasColumnType("integer") - .HasColumnName("visibility"); - - b.HasKey("Id") - .HasName("pk_posts"); - - b.HasIndex("ForwardedPostId") - .HasDatabaseName("ix_posts_forwarded_post_id"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_posts_publisher_id"); - - b.HasIndex("RepliedPostId") - .HasDatabaseName("ix_posts_replied_post_id"); - - b.HasIndex("SearchVector") - .HasDatabaseName("ix_posts_search_vector"); - - NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex("SearchVector"), "GIN"); - - b.ToTable("posts", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostCategory", 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("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("name"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_categories"); - - b.ToTable("post_categories", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostCollection", 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("Description") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("name"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_collections"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_post_collections_publisher_id"); - - b.ToTable("post_collections", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostReaction", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("Attitude") - .HasColumnType("integer") - .HasColumnName("attitude"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("PostId") - .HasColumnType("uuid") - .HasColumnName("post_id"); - - b.Property("Symbol") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("symbol"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_reactions"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_post_reactions_account_id"); - - b.HasIndex("PostId") - .HasDatabaseName("ix_post_reactions_post_id"); - - b.ToTable("post_reactions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostTag", 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("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("name"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_tags"); - - b.ToTable("post_tags", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.Publisher", 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("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_id"); - - b.Property("Bio") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("bio"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - 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("Picture") - .HasColumnType("jsonb") - .HasColumnName("picture"); - - b.Property("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - b.Property("RealmId") - .HasColumnType("uuid") - .HasColumnName("realm_id"); - - b.Property("Type") - .HasColumnType("integer") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_publishers"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_publishers_account_id"); - - b.HasIndex("Name") - .IsUnique() - .HasDatabaseName("ix_publishers_name"); - - b.HasIndex("RealmId") - .HasDatabaseName("ix_publishers_realm_id"); - - b.ToTable("publishers", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherFeature", 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("ExpiredAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expired_at"); - - b.Property("Flag") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("flag"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_publisher_features"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_publisher_features_publisher_id"); - - b.ToTable("publisher_features", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherMember", b => - { - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_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("Role") - .HasColumnType("integer") - .HasColumnName("role"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("PublisherId", "AccountId") - .HasName("pk_publisher_members"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_publisher_members_account_id"); - - b.ToTable("publisher_members", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherSubscription", 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("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("Status") - .HasColumnType("integer") - .HasColumnName("status"); - - b.Property("Tier") - .HasColumnType("integer") - .HasColumnName("tier"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_publisher_subscriptions"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_publisher_subscriptions_account_id"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_publisher_subscriptions_publisher_id"); - - b.ToTable("publisher_subscriptions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.Realm", 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("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_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("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("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - 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("VerifiedAs") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("verified_as"); - - b.Property("VerifiedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("verified_at"); - - b.HasKey("Id") - .HasName("pk_realms"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_realms_account_id"); - - b.HasIndex("Slug") - .IsUnique() - .HasDatabaseName("ix_realms_slug"); - - b.ToTable("realms", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.RealmMember", 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.HasIndex("AccountId") - .HasDatabaseName("ix_realm_members_account_id"); - - b.ToTable("realm_members", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.Sticker", 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("Image") - .HasColumnType("jsonb") - .HasColumnName("image"); - - b.Property("ImageId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("image_id"); - - b.Property("PackId") - .HasColumnType("uuid") - .HasColumnName("pack_id"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_stickers"); - - b.HasIndex("PackId") - .HasDatabaseName("ix_stickers_pack_id"); - - b.HasIndex("Slug") - .HasDatabaseName("ix_stickers_slug"); - - b.ToTable("stickers", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.StickerPack", 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("Description") - .IsRequired() - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("Prefix") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("prefix"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_sticker_packs"); - - b.HasIndex("Prefix") - .IsUnique() - .HasDatabaseName("ix_sticker_packs_prefix"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_sticker_packs_publisher_id"); - - b.ToTable("sticker_packs", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFile", b => - { - b.Property("Id") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .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("Description") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property>("FileMeta") - .HasColumnType("jsonb") - .HasColumnName("file_meta"); - - b.Property("HasCompression") - .HasColumnType("boolean") - .HasColumnName("has_compression"); - - b.Property("Hash") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("hash"); - - b.Property("MessageId") - .HasColumnType("uuid") - .HasColumnName("message_id"); - - b.Property("MimeType") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("mime_type"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("PostId") - .HasColumnType("uuid") - .HasColumnName("post_id"); - - b.Property>("SensitiveMarks") - .HasColumnType("jsonb") - .HasColumnName("sensitive_marks"); - - b.Property("Size") - .HasColumnType("bigint") - .HasColumnName("size"); - - b.Property("StorageId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("storage_id"); - - b.Property("StorageUrl") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("storage_url"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("UploadedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("uploaded_at"); - - b.Property("UploadedTo") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("uploaded_to"); - - b.Property>("UserMeta") - .HasColumnType("jsonb") - .HasColumnName("user_meta"); - - b.HasKey("Id") - .HasName("pk_files"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_files_account_id"); - - b.HasIndex("MessageId") - .HasDatabaseName("ix_files_message_id"); - - b.HasIndex("PostId") - .HasDatabaseName("ix_files_post_id"); - - b.ToTable("files", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFileReference", 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("ExpiredAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expired_at"); - - b.Property("FileId") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("file_id"); - - b.Property("ResourceId") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("resource_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("Usage") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("usage"); - - b.HasKey("Id") - .HasName("pk_file_references"); - - b.HasIndex("FileId") - .HasDatabaseName("ix_file_references_file_id"); - - b.ToTable("file_references", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Order", 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("ExpiredAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expired_at"); - - b.Property("IssuerAppId") - .HasColumnType("uuid") - .HasColumnName("issuer_app_id"); - - b.Property("PayeeWalletId") - .HasColumnType("uuid") - .HasColumnName("payee_wallet_id"); - - 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("IssuerAppId") - .HasDatabaseName("ix_payment_orders_issuer_app_id"); - - 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.Sphere.Wallet.Transaction", 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.Sphere.Wallet.Wallet", 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.Sphere.Wallet.WalletPocket", 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("PostPostCategory", b => - { - b.Property("CategoriesId") - .HasColumnType("uuid") - .HasColumnName("categories_id"); - - b.Property("PostsId") - .HasColumnType("uuid") - .HasColumnName("posts_id"); - - b.HasKey("CategoriesId", "PostsId") - .HasName("pk_post_category_links"); - - b.HasIndex("PostsId") - .HasDatabaseName("ix_post_category_links_posts_id"); - - b.ToTable("post_category_links", (string)null); - }); - - modelBuilder.Entity("PostPostCollection", b => - { - b.Property("CollectionsId") - .HasColumnType("uuid") - .HasColumnName("collections_id"); - - b.Property("PostsId") - .HasColumnType("uuid") - .HasColumnName("posts_id"); - - b.HasKey("CollectionsId", "PostsId") - .HasName("pk_post_collection_links"); - - b.HasIndex("PostsId") - .HasDatabaseName("ix_post_collection_links_posts_id"); - - b.ToTable("post_collection_links", (string)null); - }); - - modelBuilder.Entity("PostPostTag", b => - { - b.Property("PostsId") - .HasColumnType("uuid") - .HasColumnName("posts_id"); - - b.Property("TagsId") - .HasColumnType("uuid") - .HasColumnName("tags_id"); - - b.HasKey("PostsId", "TagsId") - .HasName("pk_post_tag_links"); - - b.HasIndex("TagsId") - .HasDatabaseName("ix_post_tag_links_tags_id"); - - b.ToTable("post_tag_links", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.AccountAuthFactor", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("AuthFactors") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_auth_factors_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.AccountContact", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Contacts") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_contacts_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.ActionLog", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_action_logs_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Auth.Session", "Session") - .WithMany() - .HasForeignKey("SessionId") - .HasConstraintName("fk_action_logs_auth_sessions_session_id"); - - b.Navigation("Account"); - - b.Navigation("Session"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Badge", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Badges") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_badges_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.CheckInResult", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_check_in_results_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.MagicSpell", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .HasConstraintName("fk_magic_spells_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Notification", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_notifications_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.NotificationPushSubscription", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_notification_push_subscriptions_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Profile", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithOne("Profile") - .HasForeignKey("DysonNetwork.Sphere.Account.Profile", "AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_profiles_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Relationship", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("OutgoingRelationships") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_relationships_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Account.Account", "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.Sphere.Account.Status", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_statuses_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Challenge", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Challenges") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_challenges_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Session", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Sessions") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_sessions_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Auth.Challenge", "Challenge") - .WithMany() - .HasForeignKey("ChallengeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_sessions_auth_challenges_challenge_id"); - - b.Navigation("Account"); - - b.Navigation("Challenge"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatMember", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_members_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatRoom", "ChatRoom") - .WithMany("Members") - .HasForeignKey("ChatRoomId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_members_chat_rooms_chat_room_id"); - - b.Navigation("Account"); - - b.Navigation("ChatRoom"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatRoom", b => - { - b.HasOne("DysonNetwork.Sphere.Realm.Realm", "Realm") - .WithMany("ChatRooms") - .HasForeignKey("RealmId") - .HasConstraintName("fk_chat_rooms_realms_realm_id"); - - b.Navigation("Realm"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.Message", b => - { - b.HasOne("DysonNetwork.Sphere.Chat.ChatRoom", "ChatRoom") - .WithMany() - .HasForeignKey("ChatRoomId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_messages_chat_rooms_chat_room_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.Message", "ForwardedMessage") - .WithMany() - .HasForeignKey("ForwardedMessageId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_chat_messages_chat_messages_forwarded_message_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.Message", "RepliedMessage") - .WithMany() - .HasForeignKey("RepliedMessageId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_chat_messages_chat_messages_replied_message_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatMember", "Sender") - .WithMany() - .HasForeignKey("SenderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_messages_chat_members_sender_id"); - - b.Navigation("ChatRoom"); - - b.Navigation("ForwardedMessage"); - - b.Navigation("RepliedMessage"); - - b.Navigation("Sender"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.MessageReaction", b => - { - b.HasOne("DysonNetwork.Sphere.Chat.Message", "Message") - .WithMany("Reactions") - .HasForeignKey("MessageId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_reactions_chat_messages_message_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatMember", "Sender") - .WithMany() - .HasForeignKey("SenderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_reactions_chat_members_sender_id"); - - b.Navigation("Message"); - - b.Navigation("Sender"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.RealtimeCall", b => - { - b.HasOne("DysonNetwork.Sphere.Chat.ChatRoom", "Room") - .WithMany() - .HasForeignKey("RoomId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_realtime_call_chat_rooms_room_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatMember", "Sender") - .WithMany() - .HasForeignKey("SenderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_realtime_call_chat_members_sender_id"); - - b.Navigation("Room"); - - b.Navigation("Sender"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomApp", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Developer") - .WithMany() - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_custom_apps_publishers_publisher_id"); - - b.Navigation("Developer"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomAppSecret", b => - { - b.HasOne("DysonNetwork.Sphere.Developer.CustomApp", "App") - .WithMany() - .HasForeignKey("AppId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_custom_app_secrets_custom_apps_app_id"); - - b.Navigation("App"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroupMember", b => - { - b.HasOne("DysonNetwork.Sphere.Permission.PermissionGroup", "Group") - .WithMany("Members") - .HasForeignKey("GroupId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_permission_group_members_permission_groups_group_id"); - - b.Navigation("Group"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionNode", b => - { - b.HasOne("DysonNetwork.Sphere.Permission.PermissionGroup", "Group") - .WithMany("Nodes") - .HasForeignKey("GroupId") - .HasConstraintName("fk_permission_nodes_permission_groups_group_id"); - - b.Navigation("Group"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.Post", b => - { - b.HasOne("DysonNetwork.Sphere.Post.Post", "ForwardedPost") - .WithMany() - .HasForeignKey("ForwardedPostId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_posts_posts_forwarded_post_id"); - - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Posts") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_posts_publishers_publisher_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", "RepliedPost") - .WithMany() - .HasForeignKey("RepliedPostId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_posts_posts_replied_post_id"); - - b.Navigation("ForwardedPost"); - - b.Navigation("Publisher"); - - b.Navigation("RepliedPost"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostCollection", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Collections") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_collections_publishers_publisher_id"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostReaction", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_reactions_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", "Post") - .WithMany("Reactions") - .HasForeignKey("PostId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_reactions_posts_post_id"); - - b.Navigation("Account"); - - b.Navigation("Post"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.Publisher", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .HasConstraintName("fk_publishers_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Realm.Realm", "Realm") - .WithMany() - .HasForeignKey("RealmId") - .HasConstraintName("fk_publishers_realms_realm_id"); - - b.Navigation("Account"); - - b.Navigation("Realm"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherFeature", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany() - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_features_publishers_publisher_id"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherMember", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_members_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Members") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_members_publishers_publisher_id"); - - b.Navigation("Account"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherSubscription", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_subscriptions_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Subscriptions") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_subscriptions_publishers_publisher_id"); - - b.Navigation("Account"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.Realm", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_realms_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.RealmMember", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_realm_members_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Realm.Realm", "Realm") - .WithMany("Members") - .HasForeignKey("RealmId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_realm_members_realms_realm_id"); - - b.Navigation("Account"); - - b.Navigation("Realm"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.Sticker", b => - { - b.HasOne("DysonNetwork.Sphere.Sticker.StickerPack", "Pack") - .WithMany() - .HasForeignKey("PackId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_stickers_sticker_packs_pack_id"); - - b.Navigation("Pack"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.StickerPack", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany() - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_sticker_packs_publishers_publisher_id"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFile", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_files_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.Message", null) - .WithMany("OutdatedAttachments") - .HasForeignKey("MessageId") - .HasConstraintName("fk_files_chat_messages_message_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany("OutdatedAttachments") - .HasForeignKey("PostId") - .HasConstraintName("fk_files_posts_post_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFileReference", b => - { - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "File") - .WithMany() - .HasForeignKey("FileId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_file_references_files_file_id"); - - b.Navigation("File"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Order", b => - { - b.HasOne("DysonNetwork.Sphere.Developer.CustomApp", "IssuerApp") - .WithMany() - .HasForeignKey("IssuerAppId") - .HasConstraintName("fk_payment_orders_custom_apps_issuer_app_id"); - - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "PayeeWallet") - .WithMany() - .HasForeignKey("PayeeWalletId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_payment_orders_wallets_payee_wallet_id"); - - b.HasOne("DysonNetwork.Sphere.Wallet.Transaction", "Transaction") - .WithMany() - .HasForeignKey("TransactionId") - .HasConstraintName("fk_payment_orders_payment_transactions_transaction_id"); - - b.Navigation("IssuerApp"); - - b.Navigation("PayeeWallet"); - - b.Navigation("Transaction"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Transaction", b => - { - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "PayeeWallet") - .WithMany() - .HasForeignKey("PayeeWalletId") - .HasConstraintName("fk_payment_transactions_wallets_payee_wallet_id"); - - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "PayerWallet") - .WithMany() - .HasForeignKey("PayerWalletId") - .HasConstraintName("fk_payment_transactions_wallets_payer_wallet_id"); - - b.Navigation("PayeeWallet"); - - b.Navigation("PayerWallet"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Wallet", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_wallets_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.WalletPocket", b => - { - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "Wallet") - .WithMany("Pockets") - .HasForeignKey("WalletId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_wallet_pockets_wallets_wallet_id"); - - b.Navigation("Wallet"); - }); - - modelBuilder.Entity("PostPostCategory", b => - { - b.HasOne("DysonNetwork.Sphere.Post.PostCategory", null) - .WithMany() - .HasForeignKey("CategoriesId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_category_links_post_categories_categories_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany() - .HasForeignKey("PostsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_category_links_posts_posts_id"); - }); - - modelBuilder.Entity("PostPostCollection", b => - { - b.HasOne("DysonNetwork.Sphere.Post.PostCollection", null) - .WithMany() - .HasForeignKey("CollectionsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_collection_links_post_collections_collections_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany() - .HasForeignKey("PostsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_collection_links_posts_posts_id"); - }); - - modelBuilder.Entity("PostPostTag", b => - { - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany() - .HasForeignKey("PostsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_tag_links_posts_posts_id"); - - b.HasOne("DysonNetwork.Sphere.Post.PostTag", null) - .WithMany() - .HasForeignKey("TagsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_tag_links_post_tags_tags_id"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Account", b => - { - b.Navigation("AuthFactors"); - - b.Navigation("Badges"); - - b.Navigation("Challenges"); - - b.Navigation("Contacts"); - - b.Navigation("IncomingRelationships"); - - b.Navigation("OutgoingRelationships"); - - b.Navigation("Profile") - .IsRequired(); - - b.Navigation("Sessions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatRoom", b => - { - b.Navigation("Members"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.Message", b => - { - b.Navigation("OutdatedAttachments"); - - b.Navigation("Reactions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroup", b => - { - b.Navigation("Members"); - - b.Navigation("Nodes"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.Post", b => - { - b.Navigation("OutdatedAttachments"); - - b.Navigation("Reactions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.Publisher", b => - { - b.Navigation("Collections"); - - b.Navigation("Members"); - - b.Navigation("Posts"); - - b.Navigation("Subscriptions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.Realm", b => - { - b.Navigation("ChatRooms"); - - b.Navigation("Members"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Wallet", b => - { - b.Navigation("Pockets"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/DysonNetwork.Sphere/Migrations/20250608152205_RemoveActivities.cs b/DysonNetwork.Sphere/Migrations/20250608152205_RemoveActivities.cs deleted file mode 100644 index e5d715c..0000000 --- a/DysonNetwork.Sphere/Migrations/20250608152205_RemoveActivities.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System; -using System.Collections.Generic; -using Microsoft.EntityFrameworkCore.Migrations; -using NodaTime; - -#nullable disable - -namespace DysonNetwork.Sphere.Migrations -{ - /// - public partial class RemoveActivities : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "activities"); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "activities", - columns: table => new - { - id = table.Column(type: "uuid", nullable: false), - account_id = table.Column(type: "uuid", nullable: false), - created_at = table.Column(type: "timestamp with time zone", nullable: false), - deleted_at = table.Column(type: "timestamp with time zone", nullable: true), - meta = table.Column>(type: "jsonb", nullable: false), - resource_identifier = table.Column(type: "character varying(4096)", maxLength: 4096, nullable: false), - type = table.Column(type: "character varying(1024)", maxLength: 1024, nullable: false), - updated_at = table.Column(type: "timestamp with time zone", nullable: false), - users_visible = table.Column>(type: "jsonb", nullable: false), - visibility = table.Column(type: "integer", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("pk_activities", x => x.id); - table.ForeignKey( - name: "fk_activities_accounts_account_id", - column: x => x.account_id, - principalTable: "accounts", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateIndex( - name: "ix_activities_account_id", - table: "activities", - column: "account_id"); - } - } -} diff --git a/DysonNetwork.Sphere/Migrations/20250609153232_EnrichChatMembers.Designer.cs b/DysonNetwork.Sphere/Migrations/20250609153232_EnrichChatMembers.Designer.cs deleted file mode 100644 index 94216de..0000000 --- a/DysonNetwork.Sphere/Migrations/20250609153232_EnrichChatMembers.Designer.cs +++ /dev/null @@ -1,3357 +0,0 @@ -// -using System; -using System.Collections.Generic; -using System.Text.Json; -using DysonNetwork.Sphere; -using DysonNetwork.Sphere.Account; -using DysonNetwork.Sphere.Chat; -using DysonNetwork.Sphere.Storage; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using NetTopologySuite.Geometries; -using NodaTime; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; -using NpgsqlTypes; - -#nullable disable - -namespace DysonNetwork.Sphere.Migrations -{ - [DbContext(typeof(AppDatabase))] - [Migration("20250609153232_EnrichChatMembers")] - partial class EnrichChatMembers - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "9.0.3") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "postgis"); - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Account", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("ActivatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("activated_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("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("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.Sphere.Account.AccountAuthFactor", 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.Sphere.Account.AccountContact", 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("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.Sphere.Account.ActionLog", 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("geometry") - .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.HasIndex("SessionId") - .HasDatabaseName("ix_action_logs_session_id"); - - b.ToTable("action_logs", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Badge", 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("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.Sphere.Account.CheckInResult", 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("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.Sphere.Account.MagicSpell", 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.Sphere.Account.Notification", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("Content") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .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>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("Priority") - .HasColumnType("integer") - .HasColumnName("priority"); - - b.Property("Subtitle") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("subtitle"); - - b.Property("Title") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("title"); - - b.Property("Topic") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("topic"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("ViewedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("viewed_at"); - - b.HasKey("Id") - .HasName("pk_notifications"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_notifications_account_id"); - - b.ToTable("notifications", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.NotificationPushSubscription", 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(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("device_id"); - - b.Property("DeviceToken") - .IsRequired() - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("device_token"); - - b.Property("LastUsedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_used_at"); - - b.Property("Provider") - .HasColumnType("integer") - .HasColumnName("provider"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_notification_push_subscriptions"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_notification_push_subscriptions_account_id"); - - b.HasIndex("DeviceToken", "DeviceId", "AccountId") - .IsUnique() - .HasDatabaseName("ix_notification_push_subscriptions_device_token_device_id_acco"); - - b.ToTable("notification_push_subscriptions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Profile", 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("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_id"); - - 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("MiddleName") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("middle_name"); - - b.Property("Picture") - .HasColumnType("jsonb") - .HasColumnName("picture"); - - b.Property("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - b.Property("Pronouns") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("pronouns"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - 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.Sphere.Account.Relationship", 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.Sphere.Account.Status", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - 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("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("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.Sphere.Auth.Challenge", 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") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("device_id"); - - 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("geometry") - .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("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_challenges"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_auth_challenges_account_id"); - - b.ToTable("auth_challenges", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Session", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("ChallengeId") - .HasColumnType("uuid") - .HasColumnName("challenge_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("Label") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("label"); - - b.Property("LastGrantedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_granted_at"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - 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.ToTable("auth_sessions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatMember", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("BreakUntil") - .HasColumnType("timestamp with time zone") - .HasColumnName("break_until"); - - b.Property("ChatRoomId") - .HasColumnType("uuid") - .HasColumnName("chat_room_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("IsBot") - .HasColumnType("boolean") - .HasColumnName("is_bot"); - - b.Property("JoinedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("joined_at"); - - b.Property("LastReadAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_read_at"); - - b.Property("LeaveAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("leave_at"); - - b.Property("Nick") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("nick"); - - b.Property("Notify") - .HasColumnType("integer") - .HasColumnName("notify"); - - b.Property("Role") - .HasColumnType("integer") - .HasColumnName("role"); - - b.Property("TimeoutCause") - .HasColumnType("jsonb") - .HasColumnName("timeout_cause"); - - b.Property("TimeoutUntil") - .HasColumnType("timestamp with time zone") - .HasColumnName("timeout_until"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_chat_members"); - - b.HasAlternateKey("ChatRoomId", "AccountId") - .HasName("ak_chat_members_chat_room_id_account_id"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_chat_members_account_id"); - - b.ToTable("chat_members", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatRoom", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("Background") - .HasColumnType("jsonb") - .HasColumnName("background"); - - b.Property("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_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("Description") - .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") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("Picture") - .HasColumnType("jsonb") - .HasColumnName("picture"); - - b.Property("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - b.Property("RealmId") - .HasColumnType("uuid") - .HasColumnName("realm_id"); - - b.Property("Type") - .HasColumnType("integer") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_chat_rooms"); - - b.HasIndex("RealmId") - .HasDatabaseName("ix_chat_rooms_realm_id"); - - b.ToTable("chat_rooms", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.Message", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property>("Attachments") - .IsRequired() - .HasColumnType("jsonb") - .HasColumnName("attachments"); - - b.Property("ChatRoomId") - .HasColumnType("uuid") - .HasColumnName("chat_room_id"); - - b.Property("Content") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .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("EditedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("edited_at"); - - b.Property("ForwardedMessageId") - .HasColumnType("uuid") - .HasColumnName("forwarded_message_id"); - - b.Property>("MembersMentioned") - .HasColumnType("jsonb") - .HasColumnName("members_mentioned"); - - b.Property>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("Nonce") - .IsRequired() - .HasMaxLength(36) - .HasColumnType("character varying(36)") - .HasColumnName("nonce"); - - b.Property("RepliedMessageId") - .HasColumnType("uuid") - .HasColumnName("replied_message_id"); - - b.Property("SenderId") - .HasColumnType("uuid") - .HasColumnName("sender_id"); - - 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_chat_messages"); - - b.HasIndex("ChatRoomId") - .HasDatabaseName("ix_chat_messages_chat_room_id"); - - b.HasIndex("ForwardedMessageId") - .HasDatabaseName("ix_chat_messages_forwarded_message_id"); - - b.HasIndex("RepliedMessageId") - .HasDatabaseName("ix_chat_messages_replied_message_id"); - - b.HasIndex("SenderId") - .HasDatabaseName("ix_chat_messages_sender_id"); - - b.ToTable("chat_messages", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.MessageReaction", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("Attitude") - .HasColumnType("integer") - .HasColumnName("attitude"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("MessageId") - .HasColumnType("uuid") - .HasColumnName("message_id"); - - b.Property("SenderId") - .HasColumnType("uuid") - .HasColumnName("sender_id"); - - b.Property("Symbol") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("symbol"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_chat_reactions"); - - b.HasIndex("MessageId") - .HasDatabaseName("ix_chat_reactions_message_id"); - - b.HasIndex("SenderId") - .HasDatabaseName("ix_chat_reactions_sender_id"); - - b.ToTable("chat_reactions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.RealtimeCall", 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("EndedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("ended_at"); - - b.Property("ProviderName") - .HasColumnType("text") - .HasColumnName("provider_name"); - - b.Property("RoomId") - .HasColumnType("uuid") - .HasColumnName("room_id"); - - b.Property("SenderId") - .HasColumnType("uuid") - .HasColumnName("sender_id"); - - b.Property("SessionId") - .HasColumnType("text") - .HasColumnName("session_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("UpstreamConfigJson") - .HasColumnType("jsonb") - .HasColumnName("upstream"); - - b.HasKey("Id") - .HasName("pk_chat_realtime_call"); - - b.HasIndex("RoomId") - .HasDatabaseName("ix_chat_realtime_call_room_id"); - - b.HasIndex("SenderId") - .HasDatabaseName("ix_chat_realtime_call_sender_id"); - - b.ToTable("chat_realtime_call", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomApp", 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("Name") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("slug"); - - b.Property("Status") - .HasColumnType("integer") - .HasColumnName("status"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("VerifiedAs") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("verified_as"); - - b.Property("VerifiedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("verified_at"); - - b.HasKey("Id") - .HasName("pk_custom_apps"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_custom_apps_publisher_id"); - - b.ToTable("custom_apps", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomAppSecret", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AppId") - .HasColumnType("uuid") - .HasColumnName("app_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("Remarks") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("remarks"); - - b.Property("Secret") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("secret"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_custom_app_secrets"); - - b.HasIndex("AppId") - .HasDatabaseName("ix_custom_app_secrets_app_id"); - - b.ToTable("custom_app_secrets", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroup", 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.Sphere.Permission.PermissionGroupMember", 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.Sphere.Permission.PermissionNode", 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("Area") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("area"); - - 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("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", "Area", "Actor") - .HasDatabaseName("ix_permission_nodes_key_area_actor"); - - b.ToTable("permission_nodes", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.Post", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property>("Attachments") - .IsRequired() - .HasColumnType("jsonb") - .HasColumnName("attachments"); - - b.Property("Content") - .HasColumnType("text") - .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("Description") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("Downvotes") - .HasColumnType("integer") - .HasColumnName("downvotes"); - - b.Property("EditedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("edited_at"); - - b.Property("ForwardedPostId") - .HasColumnType("uuid") - .HasColumnName("forwarded_post_id"); - - b.Property("Language") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("language"); - - b.Property>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("PublishedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("published_at"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("RepliedPostId") - .HasColumnType("uuid") - .HasColumnName("replied_post_id"); - - b.Property("SearchVector") - .IsRequired() - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("tsvector") - .HasColumnName("search_vector") - .HasAnnotation("Npgsql:TsVectorConfig", "simple") - .HasAnnotation("Npgsql:TsVectorProperties", new[] { "Title", "Description", "Content" }); - - b.Property("Title") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("title"); - - b.Property("Type") - .HasColumnType("integer") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("Upvotes") - .HasColumnType("integer") - .HasColumnName("upvotes"); - - b.Property("ViewsTotal") - .HasColumnType("integer") - .HasColumnName("views_total"); - - b.Property("ViewsUnique") - .HasColumnType("integer") - .HasColumnName("views_unique"); - - b.Property("Visibility") - .HasColumnType("integer") - .HasColumnName("visibility"); - - b.HasKey("Id") - .HasName("pk_posts"); - - b.HasIndex("ForwardedPostId") - .HasDatabaseName("ix_posts_forwarded_post_id"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_posts_publisher_id"); - - b.HasIndex("RepliedPostId") - .HasDatabaseName("ix_posts_replied_post_id"); - - b.HasIndex("SearchVector") - .HasDatabaseName("ix_posts_search_vector"); - - NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex("SearchVector"), "GIN"); - - b.ToTable("posts", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostCategory", 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("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("name"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_categories"); - - b.ToTable("post_categories", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostCollection", 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("Description") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("name"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_collections"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_post_collections_publisher_id"); - - b.ToTable("post_collections", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostReaction", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("Attitude") - .HasColumnType("integer") - .HasColumnName("attitude"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("PostId") - .HasColumnType("uuid") - .HasColumnName("post_id"); - - b.Property("Symbol") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("symbol"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_reactions"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_post_reactions_account_id"); - - b.HasIndex("PostId") - .HasDatabaseName("ix_post_reactions_post_id"); - - b.ToTable("post_reactions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostTag", 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("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("name"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_tags"); - - b.ToTable("post_tags", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.Publisher", 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("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_id"); - - b.Property("Bio") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("bio"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - 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("Picture") - .HasColumnType("jsonb") - .HasColumnName("picture"); - - b.Property("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - b.Property("RealmId") - .HasColumnType("uuid") - .HasColumnName("realm_id"); - - b.Property("Type") - .HasColumnType("integer") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_publishers"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_publishers_account_id"); - - b.HasIndex("Name") - .IsUnique() - .HasDatabaseName("ix_publishers_name"); - - b.HasIndex("RealmId") - .HasDatabaseName("ix_publishers_realm_id"); - - b.ToTable("publishers", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherFeature", 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("ExpiredAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expired_at"); - - b.Property("Flag") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("flag"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_publisher_features"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_publisher_features_publisher_id"); - - b.ToTable("publisher_features", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherMember", b => - { - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_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("Role") - .HasColumnType("integer") - .HasColumnName("role"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("PublisherId", "AccountId") - .HasName("pk_publisher_members"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_publisher_members_account_id"); - - b.ToTable("publisher_members", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherSubscription", 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("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("Status") - .HasColumnType("integer") - .HasColumnName("status"); - - b.Property("Tier") - .HasColumnType("integer") - .HasColumnName("tier"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_publisher_subscriptions"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_publisher_subscriptions_account_id"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_publisher_subscriptions_publisher_id"); - - b.ToTable("publisher_subscriptions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.Realm", 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("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_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("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("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - 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("VerifiedAs") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("verified_as"); - - b.Property("VerifiedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("verified_at"); - - b.HasKey("Id") - .HasName("pk_realms"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_realms_account_id"); - - b.HasIndex("Slug") - .IsUnique() - .HasDatabaseName("ix_realms_slug"); - - b.ToTable("realms", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.RealmMember", 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.HasIndex("AccountId") - .HasDatabaseName("ix_realm_members_account_id"); - - b.ToTable("realm_members", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.Sticker", 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("Image") - .HasColumnType("jsonb") - .HasColumnName("image"); - - b.Property("ImageId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("image_id"); - - b.Property("PackId") - .HasColumnType("uuid") - .HasColumnName("pack_id"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_stickers"); - - b.HasIndex("PackId") - .HasDatabaseName("ix_stickers_pack_id"); - - b.HasIndex("Slug") - .HasDatabaseName("ix_stickers_slug"); - - b.ToTable("stickers", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.StickerPack", 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("Description") - .IsRequired() - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("Prefix") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("prefix"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_sticker_packs"); - - b.HasIndex("Prefix") - .IsUnique() - .HasDatabaseName("ix_sticker_packs_prefix"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_sticker_packs_publisher_id"); - - b.ToTable("sticker_packs", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFile", b => - { - b.Property("Id") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .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("Description") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property>("FileMeta") - .HasColumnType("jsonb") - .HasColumnName("file_meta"); - - b.Property("HasCompression") - .HasColumnType("boolean") - .HasColumnName("has_compression"); - - b.Property("Hash") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("hash"); - - b.Property("MessageId") - .HasColumnType("uuid") - .HasColumnName("message_id"); - - b.Property("MimeType") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("mime_type"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("PostId") - .HasColumnType("uuid") - .HasColumnName("post_id"); - - b.Property>("SensitiveMarks") - .HasColumnType("jsonb") - .HasColumnName("sensitive_marks"); - - b.Property("Size") - .HasColumnType("bigint") - .HasColumnName("size"); - - b.Property("StorageId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("storage_id"); - - b.Property("StorageUrl") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("storage_url"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("UploadedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("uploaded_at"); - - b.Property("UploadedTo") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("uploaded_to"); - - b.Property>("UserMeta") - .HasColumnType("jsonb") - .HasColumnName("user_meta"); - - b.HasKey("Id") - .HasName("pk_files"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_files_account_id"); - - b.HasIndex("MessageId") - .HasDatabaseName("ix_files_message_id"); - - b.HasIndex("PostId") - .HasDatabaseName("ix_files_post_id"); - - b.ToTable("files", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFileReference", 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("ExpiredAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expired_at"); - - b.Property("FileId") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("file_id"); - - b.Property("ResourceId") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("resource_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("Usage") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("usage"); - - b.HasKey("Id") - .HasName("pk_file_references"); - - b.HasIndex("FileId") - .HasDatabaseName("ix_file_references_file_id"); - - b.ToTable("file_references", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Order", 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("ExpiredAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expired_at"); - - b.Property("IssuerAppId") - .HasColumnType("uuid") - .HasColumnName("issuer_app_id"); - - b.Property("PayeeWalletId") - .HasColumnType("uuid") - .HasColumnName("payee_wallet_id"); - - 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("IssuerAppId") - .HasDatabaseName("ix_payment_orders_issuer_app_id"); - - 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.Sphere.Wallet.Transaction", 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.Sphere.Wallet.Wallet", 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.Sphere.Wallet.WalletPocket", 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("PostPostCategory", b => - { - b.Property("CategoriesId") - .HasColumnType("uuid") - .HasColumnName("categories_id"); - - b.Property("PostsId") - .HasColumnType("uuid") - .HasColumnName("posts_id"); - - b.HasKey("CategoriesId", "PostsId") - .HasName("pk_post_category_links"); - - b.HasIndex("PostsId") - .HasDatabaseName("ix_post_category_links_posts_id"); - - b.ToTable("post_category_links", (string)null); - }); - - modelBuilder.Entity("PostPostCollection", b => - { - b.Property("CollectionsId") - .HasColumnType("uuid") - .HasColumnName("collections_id"); - - b.Property("PostsId") - .HasColumnType("uuid") - .HasColumnName("posts_id"); - - b.HasKey("CollectionsId", "PostsId") - .HasName("pk_post_collection_links"); - - b.HasIndex("PostsId") - .HasDatabaseName("ix_post_collection_links_posts_id"); - - b.ToTable("post_collection_links", (string)null); - }); - - modelBuilder.Entity("PostPostTag", b => - { - b.Property("PostsId") - .HasColumnType("uuid") - .HasColumnName("posts_id"); - - b.Property("TagsId") - .HasColumnType("uuid") - .HasColumnName("tags_id"); - - b.HasKey("PostsId", "TagsId") - .HasName("pk_post_tag_links"); - - b.HasIndex("TagsId") - .HasDatabaseName("ix_post_tag_links_tags_id"); - - b.ToTable("post_tag_links", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.AccountAuthFactor", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("AuthFactors") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_auth_factors_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.AccountContact", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Contacts") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_contacts_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.ActionLog", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_action_logs_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Auth.Session", "Session") - .WithMany() - .HasForeignKey("SessionId") - .HasConstraintName("fk_action_logs_auth_sessions_session_id"); - - b.Navigation("Account"); - - b.Navigation("Session"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Badge", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Badges") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_badges_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.CheckInResult", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_check_in_results_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.MagicSpell", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .HasConstraintName("fk_magic_spells_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Notification", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_notifications_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.NotificationPushSubscription", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_notification_push_subscriptions_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Profile", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithOne("Profile") - .HasForeignKey("DysonNetwork.Sphere.Account.Profile", "AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_profiles_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Relationship", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("OutgoingRelationships") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_relationships_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Account.Account", "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.Sphere.Account.Status", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_statuses_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Challenge", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Challenges") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_challenges_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Session", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Sessions") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_sessions_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Auth.Challenge", "Challenge") - .WithMany() - .HasForeignKey("ChallengeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_sessions_auth_challenges_challenge_id"); - - b.Navigation("Account"); - - b.Navigation("Challenge"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatMember", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_members_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatRoom", "ChatRoom") - .WithMany("Members") - .HasForeignKey("ChatRoomId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_members_chat_rooms_chat_room_id"); - - b.Navigation("Account"); - - b.Navigation("ChatRoom"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatRoom", b => - { - b.HasOne("DysonNetwork.Sphere.Realm.Realm", "Realm") - .WithMany("ChatRooms") - .HasForeignKey("RealmId") - .HasConstraintName("fk_chat_rooms_realms_realm_id"); - - b.Navigation("Realm"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.Message", b => - { - b.HasOne("DysonNetwork.Sphere.Chat.ChatRoom", "ChatRoom") - .WithMany() - .HasForeignKey("ChatRoomId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_messages_chat_rooms_chat_room_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.Message", "ForwardedMessage") - .WithMany() - .HasForeignKey("ForwardedMessageId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_chat_messages_chat_messages_forwarded_message_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.Message", "RepliedMessage") - .WithMany() - .HasForeignKey("RepliedMessageId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_chat_messages_chat_messages_replied_message_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatMember", "Sender") - .WithMany() - .HasForeignKey("SenderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_messages_chat_members_sender_id"); - - b.Navigation("ChatRoom"); - - b.Navigation("ForwardedMessage"); - - b.Navigation("RepliedMessage"); - - b.Navigation("Sender"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.MessageReaction", b => - { - b.HasOne("DysonNetwork.Sphere.Chat.Message", "Message") - .WithMany("Reactions") - .HasForeignKey("MessageId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_reactions_chat_messages_message_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatMember", "Sender") - .WithMany() - .HasForeignKey("SenderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_reactions_chat_members_sender_id"); - - b.Navigation("Message"); - - b.Navigation("Sender"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.RealtimeCall", b => - { - b.HasOne("DysonNetwork.Sphere.Chat.ChatRoom", "Room") - .WithMany() - .HasForeignKey("RoomId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_realtime_call_chat_rooms_room_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatMember", "Sender") - .WithMany() - .HasForeignKey("SenderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_realtime_call_chat_members_sender_id"); - - b.Navigation("Room"); - - b.Navigation("Sender"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomApp", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Developer") - .WithMany() - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_custom_apps_publishers_publisher_id"); - - b.Navigation("Developer"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomAppSecret", b => - { - b.HasOne("DysonNetwork.Sphere.Developer.CustomApp", "App") - .WithMany() - .HasForeignKey("AppId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_custom_app_secrets_custom_apps_app_id"); - - b.Navigation("App"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroupMember", b => - { - b.HasOne("DysonNetwork.Sphere.Permission.PermissionGroup", "Group") - .WithMany("Members") - .HasForeignKey("GroupId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_permission_group_members_permission_groups_group_id"); - - b.Navigation("Group"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionNode", b => - { - b.HasOne("DysonNetwork.Sphere.Permission.PermissionGroup", "Group") - .WithMany("Nodes") - .HasForeignKey("GroupId") - .HasConstraintName("fk_permission_nodes_permission_groups_group_id"); - - b.Navigation("Group"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.Post", b => - { - b.HasOne("DysonNetwork.Sphere.Post.Post", "ForwardedPost") - .WithMany() - .HasForeignKey("ForwardedPostId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_posts_posts_forwarded_post_id"); - - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Posts") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_posts_publishers_publisher_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", "RepliedPost") - .WithMany() - .HasForeignKey("RepliedPostId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_posts_posts_replied_post_id"); - - b.Navigation("ForwardedPost"); - - b.Navigation("Publisher"); - - b.Navigation("RepliedPost"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostCollection", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Collections") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_collections_publishers_publisher_id"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostReaction", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_reactions_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", "Post") - .WithMany("Reactions") - .HasForeignKey("PostId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_reactions_posts_post_id"); - - b.Navigation("Account"); - - b.Navigation("Post"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.Publisher", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .HasConstraintName("fk_publishers_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Realm.Realm", "Realm") - .WithMany() - .HasForeignKey("RealmId") - .HasConstraintName("fk_publishers_realms_realm_id"); - - b.Navigation("Account"); - - b.Navigation("Realm"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherFeature", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany() - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_features_publishers_publisher_id"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherMember", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_members_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Members") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_members_publishers_publisher_id"); - - b.Navigation("Account"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherSubscription", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_subscriptions_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Subscriptions") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_subscriptions_publishers_publisher_id"); - - b.Navigation("Account"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.Realm", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_realms_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.RealmMember", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_realm_members_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Realm.Realm", "Realm") - .WithMany("Members") - .HasForeignKey("RealmId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_realm_members_realms_realm_id"); - - b.Navigation("Account"); - - b.Navigation("Realm"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.Sticker", b => - { - b.HasOne("DysonNetwork.Sphere.Sticker.StickerPack", "Pack") - .WithMany() - .HasForeignKey("PackId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_stickers_sticker_packs_pack_id"); - - b.Navigation("Pack"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.StickerPack", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany() - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_sticker_packs_publishers_publisher_id"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFile", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_files_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.Message", null) - .WithMany("OutdatedAttachments") - .HasForeignKey("MessageId") - .HasConstraintName("fk_files_chat_messages_message_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany("OutdatedAttachments") - .HasForeignKey("PostId") - .HasConstraintName("fk_files_posts_post_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFileReference", b => - { - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "File") - .WithMany() - .HasForeignKey("FileId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_file_references_files_file_id"); - - b.Navigation("File"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Order", b => - { - b.HasOne("DysonNetwork.Sphere.Developer.CustomApp", "IssuerApp") - .WithMany() - .HasForeignKey("IssuerAppId") - .HasConstraintName("fk_payment_orders_custom_apps_issuer_app_id"); - - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "PayeeWallet") - .WithMany() - .HasForeignKey("PayeeWalletId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_payment_orders_wallets_payee_wallet_id"); - - b.HasOne("DysonNetwork.Sphere.Wallet.Transaction", "Transaction") - .WithMany() - .HasForeignKey("TransactionId") - .HasConstraintName("fk_payment_orders_payment_transactions_transaction_id"); - - b.Navigation("IssuerApp"); - - b.Navigation("PayeeWallet"); - - b.Navigation("Transaction"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Transaction", b => - { - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "PayeeWallet") - .WithMany() - .HasForeignKey("PayeeWalletId") - .HasConstraintName("fk_payment_transactions_wallets_payee_wallet_id"); - - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "PayerWallet") - .WithMany() - .HasForeignKey("PayerWalletId") - .HasConstraintName("fk_payment_transactions_wallets_payer_wallet_id"); - - b.Navigation("PayeeWallet"); - - b.Navigation("PayerWallet"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Wallet", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_wallets_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.WalletPocket", b => - { - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "Wallet") - .WithMany("Pockets") - .HasForeignKey("WalletId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_wallet_pockets_wallets_wallet_id"); - - b.Navigation("Wallet"); - }); - - modelBuilder.Entity("PostPostCategory", b => - { - b.HasOne("DysonNetwork.Sphere.Post.PostCategory", null) - .WithMany() - .HasForeignKey("CategoriesId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_category_links_post_categories_categories_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany() - .HasForeignKey("PostsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_category_links_posts_posts_id"); - }); - - modelBuilder.Entity("PostPostCollection", b => - { - b.HasOne("DysonNetwork.Sphere.Post.PostCollection", null) - .WithMany() - .HasForeignKey("CollectionsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_collection_links_post_collections_collections_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany() - .HasForeignKey("PostsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_collection_links_posts_posts_id"); - }); - - modelBuilder.Entity("PostPostTag", b => - { - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany() - .HasForeignKey("PostsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_tag_links_posts_posts_id"); - - b.HasOne("DysonNetwork.Sphere.Post.PostTag", null) - .WithMany() - .HasForeignKey("TagsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_tag_links_post_tags_tags_id"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Account", b => - { - b.Navigation("AuthFactors"); - - b.Navigation("Badges"); - - b.Navigation("Challenges"); - - b.Navigation("Contacts"); - - b.Navigation("IncomingRelationships"); - - b.Navigation("OutgoingRelationships"); - - b.Navigation("Profile") - .IsRequired(); - - b.Navigation("Sessions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatRoom", b => - { - b.Navigation("Members"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.Message", b => - { - b.Navigation("OutdatedAttachments"); - - b.Navigation("Reactions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroup", b => - { - b.Navigation("Members"); - - b.Navigation("Nodes"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.Post", b => - { - b.Navigation("OutdatedAttachments"); - - b.Navigation("Reactions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.Publisher", b => - { - b.Navigation("Collections"); - - b.Navigation("Members"); - - b.Navigation("Posts"); - - b.Navigation("Subscriptions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.Realm", b => - { - b.Navigation("ChatRooms"); - - b.Navigation("Members"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Wallet", b => - { - b.Navigation("Pockets"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/DysonNetwork.Sphere/Migrations/20250609153232_EnrichChatMembers.cs b/DysonNetwork.Sphere/Migrations/20250609153232_EnrichChatMembers.cs deleted file mode 100644 index 0f25d8f..0000000 --- a/DysonNetwork.Sphere/Migrations/20250609153232_EnrichChatMembers.cs +++ /dev/null @@ -1,50 +0,0 @@ -using DysonNetwork.Sphere.Chat; -using Microsoft.EntityFrameworkCore.Migrations; -using NodaTime; - -#nullable disable - -namespace DysonNetwork.Sphere.Migrations -{ - /// - public partial class EnrichChatMembers : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "break_until", - table: "chat_members", - type: "timestamp with time zone", - nullable: true); - - migrationBuilder.AddColumn( - name: "timeout_cause", - table: "chat_members", - type: "jsonb", - nullable: true); - - migrationBuilder.AddColumn( - name: "timeout_until", - table: "chat_members", - type: "timestamp with time zone", - nullable: true); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "break_until", - table: "chat_members"); - - migrationBuilder.DropColumn( - name: "timeout_cause", - table: "chat_members"); - - migrationBuilder.DropColumn( - name: "timeout_until", - table: "chat_members"); - } - } -} diff --git a/DysonNetwork.Sphere/Migrations/20250610151739_ActiveBadgeAndVerification.Designer.cs b/DysonNetwork.Sphere/Migrations/20250610151739_ActiveBadgeAndVerification.Designer.cs deleted file mode 100644 index 82f5e94..0000000 --- a/DysonNetwork.Sphere/Migrations/20250610151739_ActiveBadgeAndVerification.Designer.cs +++ /dev/null @@ -1,3368 +0,0 @@ -// -using System; -using System.Collections.Generic; -using System.Text.Json; -using DysonNetwork.Sphere; -using DysonNetwork.Sphere.Account; -using DysonNetwork.Sphere.Chat; -using DysonNetwork.Sphere.Storage; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using NetTopologySuite.Geometries; -using NodaTime; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; -using NpgsqlTypes; - -#nullable disable - -namespace DysonNetwork.Sphere.Migrations -{ - [DbContext(typeof(AppDatabase))] - [Migration("20250610151739_ActiveBadgeAndVerification")] - partial class ActiveBadgeAndVerification - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "9.0.3") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "postgis"); - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Account", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("ActivatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("activated_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("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("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.Sphere.Account.AccountAuthFactor", 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.Sphere.Account.AccountContact", 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("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.Sphere.Account.ActionLog", 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("geometry") - .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.HasIndex("SessionId") - .HasDatabaseName("ix_action_logs_session_id"); - - b.ToTable("action_logs", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Badge", 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.Sphere.Account.CheckInResult", 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("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.Sphere.Account.MagicSpell", 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.Sphere.Account.Notification", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("Content") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .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>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("Priority") - .HasColumnType("integer") - .HasColumnName("priority"); - - b.Property("Subtitle") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("subtitle"); - - b.Property("Title") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("title"); - - b.Property("Topic") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("topic"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("ViewedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("viewed_at"); - - b.HasKey("Id") - .HasName("pk_notifications"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_notifications_account_id"); - - b.ToTable("notifications", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.NotificationPushSubscription", 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(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("device_id"); - - b.Property("DeviceToken") - .IsRequired() - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("device_token"); - - b.Property("LastUsedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_used_at"); - - b.Property("Provider") - .HasColumnType("integer") - .HasColumnName("provider"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_notification_push_subscriptions"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_notification_push_subscriptions_account_id"); - - b.HasIndex("DeviceToken", "DeviceId", "AccountId") - .IsUnique() - .HasDatabaseName("ix_notification_push_subscriptions_device_token_device_id_acco"); - - b.ToTable("notification_push_subscriptions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Profile", 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("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_id"); - - 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("MiddleName") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("middle_name"); - - b.Property("Picture") - .HasColumnType("jsonb") - .HasColumnName("picture"); - - b.Property("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - b.Property("Pronouns") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("pronouns"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - 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.Sphere.Account.Relationship", 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.Sphere.Account.Status", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - 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("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("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.Sphere.Auth.Challenge", 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") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("device_id"); - - 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("geometry") - .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("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_challenges"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_auth_challenges_account_id"); - - b.ToTable("auth_challenges", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Session", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("ChallengeId") - .HasColumnType("uuid") - .HasColumnName("challenge_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("Label") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("label"); - - b.Property("LastGrantedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_granted_at"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - 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.ToTable("auth_sessions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatMember", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("BreakUntil") - .HasColumnType("timestamp with time zone") - .HasColumnName("break_until"); - - b.Property("ChatRoomId") - .HasColumnType("uuid") - .HasColumnName("chat_room_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("IsBot") - .HasColumnType("boolean") - .HasColumnName("is_bot"); - - b.Property("JoinedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("joined_at"); - - b.Property("LastReadAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_read_at"); - - b.Property("LeaveAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("leave_at"); - - b.Property("Nick") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("nick"); - - b.Property("Notify") - .HasColumnType("integer") - .HasColumnName("notify"); - - b.Property("Role") - .HasColumnType("integer") - .HasColumnName("role"); - - b.Property("TimeoutCause") - .HasColumnType("jsonb") - .HasColumnName("timeout_cause"); - - b.Property("TimeoutUntil") - .HasColumnType("timestamp with time zone") - .HasColumnName("timeout_until"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_chat_members"); - - b.HasAlternateKey("ChatRoomId", "AccountId") - .HasName("ak_chat_members_chat_room_id_account_id"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_chat_members_account_id"); - - b.ToTable("chat_members", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatRoom", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("Background") - .HasColumnType("jsonb") - .HasColumnName("background"); - - b.Property("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_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("Description") - .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") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("Picture") - .HasColumnType("jsonb") - .HasColumnName("picture"); - - b.Property("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - b.Property("RealmId") - .HasColumnType("uuid") - .HasColumnName("realm_id"); - - b.Property("Type") - .HasColumnType("integer") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_chat_rooms"); - - b.HasIndex("RealmId") - .HasDatabaseName("ix_chat_rooms_realm_id"); - - b.ToTable("chat_rooms", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.Message", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property>("Attachments") - .IsRequired() - .HasColumnType("jsonb") - .HasColumnName("attachments"); - - b.Property("ChatRoomId") - .HasColumnType("uuid") - .HasColumnName("chat_room_id"); - - b.Property("Content") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .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("EditedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("edited_at"); - - b.Property("ForwardedMessageId") - .HasColumnType("uuid") - .HasColumnName("forwarded_message_id"); - - b.Property>("MembersMentioned") - .HasColumnType("jsonb") - .HasColumnName("members_mentioned"); - - b.Property>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("Nonce") - .IsRequired() - .HasMaxLength(36) - .HasColumnType("character varying(36)") - .HasColumnName("nonce"); - - b.Property("RepliedMessageId") - .HasColumnType("uuid") - .HasColumnName("replied_message_id"); - - b.Property("SenderId") - .HasColumnType("uuid") - .HasColumnName("sender_id"); - - 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_chat_messages"); - - b.HasIndex("ChatRoomId") - .HasDatabaseName("ix_chat_messages_chat_room_id"); - - b.HasIndex("ForwardedMessageId") - .HasDatabaseName("ix_chat_messages_forwarded_message_id"); - - b.HasIndex("RepliedMessageId") - .HasDatabaseName("ix_chat_messages_replied_message_id"); - - b.HasIndex("SenderId") - .HasDatabaseName("ix_chat_messages_sender_id"); - - b.ToTable("chat_messages", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.MessageReaction", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("Attitude") - .HasColumnType("integer") - .HasColumnName("attitude"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("MessageId") - .HasColumnType("uuid") - .HasColumnName("message_id"); - - b.Property("SenderId") - .HasColumnType("uuid") - .HasColumnName("sender_id"); - - b.Property("Symbol") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("symbol"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_chat_reactions"); - - b.HasIndex("MessageId") - .HasDatabaseName("ix_chat_reactions_message_id"); - - b.HasIndex("SenderId") - .HasDatabaseName("ix_chat_reactions_sender_id"); - - b.ToTable("chat_reactions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.RealtimeCall", 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("EndedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("ended_at"); - - b.Property("ProviderName") - .HasColumnType("text") - .HasColumnName("provider_name"); - - b.Property("RoomId") - .HasColumnType("uuid") - .HasColumnName("room_id"); - - b.Property("SenderId") - .HasColumnType("uuid") - .HasColumnName("sender_id"); - - b.Property("SessionId") - .HasColumnType("text") - .HasColumnName("session_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("UpstreamConfigJson") - .HasColumnType("jsonb") - .HasColumnName("upstream"); - - b.HasKey("Id") - .HasName("pk_chat_realtime_call"); - - b.HasIndex("RoomId") - .HasDatabaseName("ix_chat_realtime_call_room_id"); - - b.HasIndex("SenderId") - .HasDatabaseName("ix_chat_realtime_call_sender_id"); - - b.ToTable("chat_realtime_call", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomApp", 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("Name") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("slug"); - - b.Property("Status") - .HasColumnType("integer") - .HasColumnName("status"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("VerifiedAs") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("verified_as"); - - b.Property("VerifiedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("verified_at"); - - b.HasKey("Id") - .HasName("pk_custom_apps"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_custom_apps_publisher_id"); - - b.ToTable("custom_apps", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomAppSecret", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AppId") - .HasColumnType("uuid") - .HasColumnName("app_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("Remarks") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("remarks"); - - b.Property("Secret") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("secret"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_custom_app_secrets"); - - b.HasIndex("AppId") - .HasDatabaseName("ix_custom_app_secrets_app_id"); - - b.ToTable("custom_app_secrets", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroup", 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.Sphere.Permission.PermissionGroupMember", 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.Sphere.Permission.PermissionNode", 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("Area") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("area"); - - 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("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", "Area", "Actor") - .HasDatabaseName("ix_permission_nodes_key_area_actor"); - - b.ToTable("permission_nodes", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.Post", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property>("Attachments") - .IsRequired() - .HasColumnType("jsonb") - .HasColumnName("attachments"); - - b.Property("Content") - .HasColumnType("text") - .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("Description") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("Downvotes") - .HasColumnType("integer") - .HasColumnName("downvotes"); - - b.Property("EditedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("edited_at"); - - b.Property("ForwardedPostId") - .HasColumnType("uuid") - .HasColumnName("forwarded_post_id"); - - b.Property("Language") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("language"); - - b.Property>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("PublishedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("published_at"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("RepliedPostId") - .HasColumnType("uuid") - .HasColumnName("replied_post_id"); - - b.Property("SearchVector") - .IsRequired() - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("tsvector") - .HasColumnName("search_vector") - .HasAnnotation("Npgsql:TsVectorConfig", "simple") - .HasAnnotation("Npgsql:TsVectorProperties", new[] { "Title", "Description", "Content" }); - - b.Property("Title") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("title"); - - b.Property("Type") - .HasColumnType("integer") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("Upvotes") - .HasColumnType("integer") - .HasColumnName("upvotes"); - - b.Property("ViewsTotal") - .HasColumnType("integer") - .HasColumnName("views_total"); - - b.Property("ViewsUnique") - .HasColumnType("integer") - .HasColumnName("views_unique"); - - b.Property("Visibility") - .HasColumnType("integer") - .HasColumnName("visibility"); - - b.HasKey("Id") - .HasName("pk_posts"); - - b.HasIndex("ForwardedPostId") - .HasDatabaseName("ix_posts_forwarded_post_id"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_posts_publisher_id"); - - b.HasIndex("RepliedPostId") - .HasDatabaseName("ix_posts_replied_post_id"); - - b.HasIndex("SearchVector") - .HasDatabaseName("ix_posts_search_vector"); - - NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex("SearchVector"), "GIN"); - - b.ToTable("posts", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostCategory", 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("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("name"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_categories"); - - b.ToTable("post_categories", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostCollection", 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("Description") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("name"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_collections"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_post_collections_publisher_id"); - - b.ToTable("post_collections", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostReaction", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("Attitude") - .HasColumnType("integer") - .HasColumnName("attitude"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("PostId") - .HasColumnType("uuid") - .HasColumnName("post_id"); - - b.Property("Symbol") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("symbol"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_reactions"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_post_reactions_account_id"); - - b.HasIndex("PostId") - .HasDatabaseName("ix_post_reactions_post_id"); - - b.ToTable("post_reactions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostTag", 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("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("name"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_tags"); - - b.ToTable("post_tags", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.Publisher", 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("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_id"); - - b.Property("Bio") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("bio"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - 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("Picture") - .HasColumnType("jsonb") - .HasColumnName("picture"); - - b.Property("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - b.Property("RealmId") - .HasColumnType("uuid") - .HasColumnName("realm_id"); - - b.Property("Type") - .HasColumnType("integer") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("Verification") - .HasColumnType("jsonb") - .HasColumnName("verification"); - - b.HasKey("Id") - .HasName("pk_publishers"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_publishers_account_id"); - - b.HasIndex("Name") - .IsUnique() - .HasDatabaseName("ix_publishers_name"); - - b.HasIndex("RealmId") - .HasDatabaseName("ix_publishers_realm_id"); - - b.ToTable("publishers", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherFeature", 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("ExpiredAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expired_at"); - - b.Property("Flag") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("flag"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_publisher_features"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_publisher_features_publisher_id"); - - b.ToTable("publisher_features", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherMember", b => - { - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_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("Role") - .HasColumnType("integer") - .HasColumnName("role"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("PublisherId", "AccountId") - .HasName("pk_publisher_members"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_publisher_members_account_id"); - - b.ToTable("publisher_members", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherSubscription", 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("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("Status") - .HasColumnType("integer") - .HasColumnName("status"); - - b.Property("Tier") - .HasColumnType("integer") - .HasColumnName("tier"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_publisher_subscriptions"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_publisher_subscriptions_account_id"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_publisher_subscriptions_publisher_id"); - - b.ToTable("publisher_subscriptions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.Realm", 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("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_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("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("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - 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("AccountId") - .HasDatabaseName("ix_realms_account_id"); - - b.HasIndex("Slug") - .IsUnique() - .HasDatabaseName("ix_realms_slug"); - - b.ToTable("realms", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.RealmMember", 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.HasIndex("AccountId") - .HasDatabaseName("ix_realm_members_account_id"); - - b.ToTable("realm_members", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.Sticker", 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("Image") - .HasColumnType("jsonb") - .HasColumnName("image"); - - b.Property("ImageId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("image_id"); - - b.Property("PackId") - .HasColumnType("uuid") - .HasColumnName("pack_id"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_stickers"); - - b.HasIndex("PackId") - .HasDatabaseName("ix_stickers_pack_id"); - - b.HasIndex("Slug") - .HasDatabaseName("ix_stickers_slug"); - - b.ToTable("stickers", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.StickerPack", 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("Description") - .IsRequired() - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("Prefix") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("prefix"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_sticker_packs"); - - b.HasIndex("Prefix") - .IsUnique() - .HasDatabaseName("ix_sticker_packs_prefix"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_sticker_packs_publisher_id"); - - b.ToTable("sticker_packs", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFile", b => - { - b.Property("Id") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .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("Description") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property>("FileMeta") - .HasColumnType("jsonb") - .HasColumnName("file_meta"); - - b.Property("HasCompression") - .HasColumnType("boolean") - .HasColumnName("has_compression"); - - b.Property("Hash") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("hash"); - - b.Property("MessageId") - .HasColumnType("uuid") - .HasColumnName("message_id"); - - b.Property("MimeType") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("mime_type"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("PostId") - .HasColumnType("uuid") - .HasColumnName("post_id"); - - b.Property>("SensitiveMarks") - .HasColumnType("jsonb") - .HasColumnName("sensitive_marks"); - - b.Property("Size") - .HasColumnType("bigint") - .HasColumnName("size"); - - b.Property("StorageId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("storage_id"); - - b.Property("StorageUrl") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("storage_url"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("UploadedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("uploaded_at"); - - b.Property("UploadedTo") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("uploaded_to"); - - b.Property>("UserMeta") - .HasColumnType("jsonb") - .HasColumnName("user_meta"); - - b.HasKey("Id") - .HasName("pk_files"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_files_account_id"); - - b.HasIndex("MessageId") - .HasDatabaseName("ix_files_message_id"); - - b.HasIndex("PostId") - .HasDatabaseName("ix_files_post_id"); - - b.ToTable("files", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFileReference", 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("ExpiredAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expired_at"); - - b.Property("FileId") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("file_id"); - - b.Property("ResourceId") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("resource_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("Usage") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("usage"); - - b.HasKey("Id") - .HasName("pk_file_references"); - - b.HasIndex("FileId") - .HasDatabaseName("ix_file_references_file_id"); - - b.ToTable("file_references", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Order", 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("ExpiredAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expired_at"); - - b.Property("IssuerAppId") - .HasColumnType("uuid") - .HasColumnName("issuer_app_id"); - - b.Property("PayeeWalletId") - .HasColumnType("uuid") - .HasColumnName("payee_wallet_id"); - - 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("IssuerAppId") - .HasDatabaseName("ix_payment_orders_issuer_app_id"); - - 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.Sphere.Wallet.Transaction", 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.Sphere.Wallet.Wallet", 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.Sphere.Wallet.WalletPocket", 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("PostPostCategory", b => - { - b.Property("CategoriesId") - .HasColumnType("uuid") - .HasColumnName("categories_id"); - - b.Property("PostsId") - .HasColumnType("uuid") - .HasColumnName("posts_id"); - - b.HasKey("CategoriesId", "PostsId") - .HasName("pk_post_category_links"); - - b.HasIndex("PostsId") - .HasDatabaseName("ix_post_category_links_posts_id"); - - b.ToTable("post_category_links", (string)null); - }); - - modelBuilder.Entity("PostPostCollection", b => - { - b.Property("CollectionsId") - .HasColumnType("uuid") - .HasColumnName("collections_id"); - - b.Property("PostsId") - .HasColumnType("uuid") - .HasColumnName("posts_id"); - - b.HasKey("CollectionsId", "PostsId") - .HasName("pk_post_collection_links"); - - b.HasIndex("PostsId") - .HasDatabaseName("ix_post_collection_links_posts_id"); - - b.ToTable("post_collection_links", (string)null); - }); - - modelBuilder.Entity("PostPostTag", b => - { - b.Property("PostsId") - .HasColumnType("uuid") - .HasColumnName("posts_id"); - - b.Property("TagsId") - .HasColumnType("uuid") - .HasColumnName("tags_id"); - - b.HasKey("PostsId", "TagsId") - .HasName("pk_post_tag_links"); - - b.HasIndex("TagsId") - .HasDatabaseName("ix_post_tag_links_tags_id"); - - b.ToTable("post_tag_links", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.AccountAuthFactor", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("AuthFactors") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_auth_factors_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.AccountContact", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Contacts") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_contacts_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.ActionLog", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_action_logs_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Auth.Session", "Session") - .WithMany() - .HasForeignKey("SessionId") - .HasConstraintName("fk_action_logs_auth_sessions_session_id"); - - b.Navigation("Account"); - - b.Navigation("Session"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Badge", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Badges") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_badges_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.CheckInResult", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_check_in_results_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.MagicSpell", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .HasConstraintName("fk_magic_spells_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Notification", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_notifications_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.NotificationPushSubscription", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_notification_push_subscriptions_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Profile", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithOne("Profile") - .HasForeignKey("DysonNetwork.Sphere.Account.Profile", "AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_profiles_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Relationship", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("OutgoingRelationships") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_relationships_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Account.Account", "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.Sphere.Account.Status", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_statuses_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Challenge", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Challenges") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_challenges_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Session", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Sessions") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_sessions_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Auth.Challenge", "Challenge") - .WithMany() - .HasForeignKey("ChallengeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_sessions_auth_challenges_challenge_id"); - - b.Navigation("Account"); - - b.Navigation("Challenge"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatMember", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_members_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatRoom", "ChatRoom") - .WithMany("Members") - .HasForeignKey("ChatRoomId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_members_chat_rooms_chat_room_id"); - - b.Navigation("Account"); - - b.Navigation("ChatRoom"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatRoom", b => - { - b.HasOne("DysonNetwork.Sphere.Realm.Realm", "Realm") - .WithMany("ChatRooms") - .HasForeignKey("RealmId") - .HasConstraintName("fk_chat_rooms_realms_realm_id"); - - b.Navigation("Realm"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.Message", b => - { - b.HasOne("DysonNetwork.Sphere.Chat.ChatRoom", "ChatRoom") - .WithMany() - .HasForeignKey("ChatRoomId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_messages_chat_rooms_chat_room_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.Message", "ForwardedMessage") - .WithMany() - .HasForeignKey("ForwardedMessageId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_chat_messages_chat_messages_forwarded_message_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.Message", "RepliedMessage") - .WithMany() - .HasForeignKey("RepliedMessageId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_chat_messages_chat_messages_replied_message_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatMember", "Sender") - .WithMany() - .HasForeignKey("SenderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_messages_chat_members_sender_id"); - - b.Navigation("ChatRoom"); - - b.Navigation("ForwardedMessage"); - - b.Navigation("RepliedMessage"); - - b.Navigation("Sender"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.MessageReaction", b => - { - b.HasOne("DysonNetwork.Sphere.Chat.Message", "Message") - .WithMany("Reactions") - .HasForeignKey("MessageId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_reactions_chat_messages_message_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatMember", "Sender") - .WithMany() - .HasForeignKey("SenderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_reactions_chat_members_sender_id"); - - b.Navigation("Message"); - - b.Navigation("Sender"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.RealtimeCall", b => - { - b.HasOne("DysonNetwork.Sphere.Chat.ChatRoom", "Room") - .WithMany() - .HasForeignKey("RoomId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_realtime_call_chat_rooms_room_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatMember", "Sender") - .WithMany() - .HasForeignKey("SenderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_realtime_call_chat_members_sender_id"); - - b.Navigation("Room"); - - b.Navigation("Sender"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomApp", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Developer") - .WithMany() - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_custom_apps_publishers_publisher_id"); - - b.Navigation("Developer"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomAppSecret", b => - { - b.HasOne("DysonNetwork.Sphere.Developer.CustomApp", "App") - .WithMany() - .HasForeignKey("AppId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_custom_app_secrets_custom_apps_app_id"); - - b.Navigation("App"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroupMember", b => - { - b.HasOne("DysonNetwork.Sphere.Permission.PermissionGroup", "Group") - .WithMany("Members") - .HasForeignKey("GroupId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_permission_group_members_permission_groups_group_id"); - - b.Navigation("Group"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionNode", b => - { - b.HasOne("DysonNetwork.Sphere.Permission.PermissionGroup", "Group") - .WithMany("Nodes") - .HasForeignKey("GroupId") - .HasConstraintName("fk_permission_nodes_permission_groups_group_id"); - - b.Navigation("Group"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.Post", b => - { - b.HasOne("DysonNetwork.Sphere.Post.Post", "ForwardedPost") - .WithMany() - .HasForeignKey("ForwardedPostId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_posts_posts_forwarded_post_id"); - - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Posts") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_posts_publishers_publisher_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", "RepliedPost") - .WithMany() - .HasForeignKey("RepliedPostId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_posts_posts_replied_post_id"); - - b.Navigation("ForwardedPost"); - - b.Navigation("Publisher"); - - b.Navigation("RepliedPost"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostCollection", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Collections") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_collections_publishers_publisher_id"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostReaction", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_reactions_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", "Post") - .WithMany("Reactions") - .HasForeignKey("PostId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_reactions_posts_post_id"); - - b.Navigation("Account"); - - b.Navigation("Post"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.Publisher", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .HasConstraintName("fk_publishers_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Realm.Realm", "Realm") - .WithMany() - .HasForeignKey("RealmId") - .HasConstraintName("fk_publishers_realms_realm_id"); - - b.Navigation("Account"); - - b.Navigation("Realm"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherFeature", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany() - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_features_publishers_publisher_id"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherMember", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_members_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Members") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_members_publishers_publisher_id"); - - b.Navigation("Account"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherSubscription", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_subscriptions_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Subscriptions") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_subscriptions_publishers_publisher_id"); - - b.Navigation("Account"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.Realm", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_realms_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.RealmMember", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_realm_members_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Realm.Realm", "Realm") - .WithMany("Members") - .HasForeignKey("RealmId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_realm_members_realms_realm_id"); - - b.Navigation("Account"); - - b.Navigation("Realm"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.Sticker", b => - { - b.HasOne("DysonNetwork.Sphere.Sticker.StickerPack", "Pack") - .WithMany() - .HasForeignKey("PackId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_stickers_sticker_packs_pack_id"); - - b.Navigation("Pack"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.StickerPack", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany() - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_sticker_packs_publishers_publisher_id"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFile", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_files_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.Message", null) - .WithMany("OutdatedAttachments") - .HasForeignKey("MessageId") - .HasConstraintName("fk_files_chat_messages_message_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany("OutdatedAttachments") - .HasForeignKey("PostId") - .HasConstraintName("fk_files_posts_post_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFileReference", b => - { - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "File") - .WithMany() - .HasForeignKey("FileId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_file_references_files_file_id"); - - b.Navigation("File"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Order", b => - { - b.HasOne("DysonNetwork.Sphere.Developer.CustomApp", "IssuerApp") - .WithMany() - .HasForeignKey("IssuerAppId") - .HasConstraintName("fk_payment_orders_custom_apps_issuer_app_id"); - - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "PayeeWallet") - .WithMany() - .HasForeignKey("PayeeWalletId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_payment_orders_wallets_payee_wallet_id"); - - b.HasOne("DysonNetwork.Sphere.Wallet.Transaction", "Transaction") - .WithMany() - .HasForeignKey("TransactionId") - .HasConstraintName("fk_payment_orders_payment_transactions_transaction_id"); - - b.Navigation("IssuerApp"); - - b.Navigation("PayeeWallet"); - - b.Navigation("Transaction"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Transaction", b => - { - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "PayeeWallet") - .WithMany() - .HasForeignKey("PayeeWalletId") - .HasConstraintName("fk_payment_transactions_wallets_payee_wallet_id"); - - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "PayerWallet") - .WithMany() - .HasForeignKey("PayerWalletId") - .HasConstraintName("fk_payment_transactions_wallets_payer_wallet_id"); - - b.Navigation("PayeeWallet"); - - b.Navigation("PayerWallet"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Wallet", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_wallets_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.WalletPocket", b => - { - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "Wallet") - .WithMany("Pockets") - .HasForeignKey("WalletId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_wallet_pockets_wallets_wallet_id"); - - b.Navigation("Wallet"); - }); - - modelBuilder.Entity("PostPostCategory", b => - { - b.HasOne("DysonNetwork.Sphere.Post.PostCategory", null) - .WithMany() - .HasForeignKey("CategoriesId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_category_links_post_categories_categories_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany() - .HasForeignKey("PostsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_category_links_posts_posts_id"); - }); - - modelBuilder.Entity("PostPostCollection", b => - { - b.HasOne("DysonNetwork.Sphere.Post.PostCollection", null) - .WithMany() - .HasForeignKey("CollectionsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_collection_links_post_collections_collections_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany() - .HasForeignKey("PostsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_collection_links_posts_posts_id"); - }); - - modelBuilder.Entity("PostPostTag", b => - { - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany() - .HasForeignKey("PostsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_tag_links_posts_posts_id"); - - b.HasOne("DysonNetwork.Sphere.Post.PostTag", null) - .WithMany() - .HasForeignKey("TagsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_tag_links_post_tags_tags_id"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Account", b => - { - b.Navigation("AuthFactors"); - - b.Navigation("Badges"); - - b.Navigation("Challenges"); - - b.Navigation("Contacts"); - - b.Navigation("IncomingRelationships"); - - b.Navigation("OutgoingRelationships"); - - b.Navigation("Profile") - .IsRequired(); - - b.Navigation("Sessions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatRoom", b => - { - b.Navigation("Members"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.Message", b => - { - b.Navigation("OutdatedAttachments"); - - b.Navigation("Reactions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroup", b => - { - b.Navigation("Members"); - - b.Navigation("Nodes"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.Post", b => - { - b.Navigation("OutdatedAttachments"); - - b.Navigation("Reactions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.Publisher", b => - { - b.Navigation("Collections"); - - b.Navigation("Members"); - - b.Navigation("Posts"); - - b.Navigation("Subscriptions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.Realm", b => - { - b.Navigation("ChatRooms"); - - b.Navigation("Members"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Wallet", b => - { - b.Navigation("Pockets"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/DysonNetwork.Sphere/Migrations/20250610151739_ActiveBadgeAndVerification.cs b/DysonNetwork.Sphere/Migrations/20250610151739_ActiveBadgeAndVerification.cs deleted file mode 100644 index a4e2dd5..0000000 --- a/DysonNetwork.Sphere/Migrations/20250610151739_ActiveBadgeAndVerification.cs +++ /dev/null @@ -1,91 +0,0 @@ -using DysonNetwork.Sphere.Account; -using Microsoft.EntityFrameworkCore.Migrations; -using NodaTime; - -#nullable disable - -namespace DysonNetwork.Sphere.Migrations -{ - /// - public partial class ActiveBadgeAndVerification : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "verified_as", - table: "realms"); - - migrationBuilder.DropColumn( - name: "verified_at", - table: "realms"); - - migrationBuilder.AddColumn( - name: "verification", - table: "realms", - type: "jsonb", - nullable: true); - - migrationBuilder.AddColumn( - name: "verification", - table: "publishers", - type: "jsonb", - nullable: true); - - migrationBuilder.AddColumn( - name: "activated_at", - table: "badges", - type: "timestamp with time zone", - nullable: true); - - migrationBuilder.AddColumn( - name: "active_badge", - table: "account_profiles", - type: "jsonb", - nullable: true); - - migrationBuilder.AddColumn( - name: "verification", - table: "account_profiles", - type: "jsonb", - nullable: true); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "verification", - table: "realms"); - - migrationBuilder.DropColumn( - name: "verification", - table: "publishers"); - - migrationBuilder.DropColumn( - name: "activated_at", - table: "badges"); - - migrationBuilder.DropColumn( - name: "active_badge", - table: "account_profiles"); - - migrationBuilder.DropColumn( - name: "verification", - table: "account_profiles"); - - migrationBuilder.AddColumn( - name: "verified_as", - table: "realms", - type: "character varying(4096)", - maxLength: 4096, - nullable: true); - - migrationBuilder.AddColumn( - name: "verified_at", - table: "realms", - type: "timestamp with time zone", - nullable: true); - } - } -} diff --git a/DysonNetwork.Sphere/Migrations/20250611165902_BetterRecyclingFilesAndWalletSubscriptions.Designer.cs b/DysonNetwork.Sphere/Migrations/20250611165902_BetterRecyclingFilesAndWalletSubscriptions.Designer.cs deleted file mode 100644 index 336f229..0000000 --- a/DysonNetwork.Sphere/Migrations/20250611165902_BetterRecyclingFilesAndWalletSubscriptions.Designer.cs +++ /dev/null @@ -1,3550 +0,0 @@ -// -using System; -using System.Collections.Generic; -using System.Text.Json; -using DysonNetwork.Sphere; -using DysonNetwork.Sphere.Account; -using DysonNetwork.Sphere.Chat; -using DysonNetwork.Sphere.Storage; -using DysonNetwork.Sphere.Wallet; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using NetTopologySuite.Geometries; -using NodaTime; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; -using NpgsqlTypes; - -#nullable disable - -namespace DysonNetwork.Sphere.Migrations -{ - [DbContext(typeof(AppDatabase))] - [Migration("20250611165902_BetterRecyclingFilesAndWalletSubscriptions")] - partial class BetterRecyclingFilesAndWalletSubscriptions - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "9.0.3") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "postgis"); - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Account", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("ActivatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("activated_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("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("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.Sphere.Account.AccountAuthFactor", 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.Sphere.Account.AccountContact", 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("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.Sphere.Account.ActionLog", 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("geometry") - .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.HasIndex("SessionId") - .HasDatabaseName("ix_action_logs_session_id"); - - b.ToTable("action_logs", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Badge", 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.Sphere.Account.CheckInResult", 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("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.Sphere.Account.MagicSpell", 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.Sphere.Account.Notification", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("Content") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .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>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("Priority") - .HasColumnType("integer") - .HasColumnName("priority"); - - b.Property("Subtitle") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("subtitle"); - - b.Property("Title") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("title"); - - b.Property("Topic") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("topic"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("ViewedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("viewed_at"); - - b.HasKey("Id") - .HasName("pk_notifications"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_notifications_account_id"); - - b.ToTable("notifications", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.NotificationPushSubscription", 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(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("device_id"); - - b.Property("DeviceToken") - .IsRequired() - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("device_token"); - - b.Property("LastUsedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_used_at"); - - b.Property("Provider") - .HasColumnType("integer") - .HasColumnName("provider"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_notification_push_subscriptions"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_notification_push_subscriptions_account_id"); - - b.HasIndex("DeviceToken", "DeviceId", "AccountId") - .IsUnique() - .HasDatabaseName("ix_notification_push_subscriptions_device_token_device_id_acco"); - - b.ToTable("notification_push_subscriptions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Profile", 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("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_id"); - - 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("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("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - b.Property("Pronouns") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("pronouns"); - - b.Property("StellarMembership") - .HasColumnType("jsonb") - .HasColumnName("stellar_membership"); - - 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("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.Sphere.Account.Relationship", 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.Sphere.Account.Status", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - 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("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("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.Sphere.Auth.Challenge", 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") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("device_id"); - - 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("geometry") - .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("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_challenges"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_auth_challenges_account_id"); - - b.ToTable("auth_challenges", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Session", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("ChallengeId") - .HasColumnType("uuid") - .HasColumnName("challenge_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("Label") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("label"); - - b.Property("LastGrantedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_granted_at"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - 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.ToTable("auth_sessions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatMember", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("BreakUntil") - .HasColumnType("timestamp with time zone") - .HasColumnName("break_until"); - - b.Property("ChatRoomId") - .HasColumnType("uuid") - .HasColumnName("chat_room_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("IsBot") - .HasColumnType("boolean") - .HasColumnName("is_bot"); - - b.Property("JoinedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("joined_at"); - - b.Property("LastReadAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_read_at"); - - b.Property("LeaveAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("leave_at"); - - b.Property("Nick") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("nick"); - - b.Property("Notify") - .HasColumnType("integer") - .HasColumnName("notify"); - - b.Property("Role") - .HasColumnType("integer") - .HasColumnName("role"); - - b.Property("TimeoutCause") - .HasColumnType("jsonb") - .HasColumnName("timeout_cause"); - - b.Property("TimeoutUntil") - .HasColumnType("timestamp with time zone") - .HasColumnName("timeout_until"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_chat_members"); - - b.HasAlternateKey("ChatRoomId", "AccountId") - .HasName("ak_chat_members_chat_room_id_account_id"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_chat_members_account_id"); - - b.ToTable("chat_members", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatRoom", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("Background") - .HasColumnType("jsonb") - .HasColumnName("background"); - - b.Property("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_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("Description") - .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") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("Picture") - .HasColumnType("jsonb") - .HasColumnName("picture"); - - b.Property("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - b.Property("RealmId") - .HasColumnType("uuid") - .HasColumnName("realm_id"); - - b.Property("Type") - .HasColumnType("integer") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_chat_rooms"); - - b.HasIndex("RealmId") - .HasDatabaseName("ix_chat_rooms_realm_id"); - - b.ToTable("chat_rooms", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.Message", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property>("Attachments") - .IsRequired() - .HasColumnType("jsonb") - .HasColumnName("attachments"); - - b.Property("ChatRoomId") - .HasColumnType("uuid") - .HasColumnName("chat_room_id"); - - b.Property("Content") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .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("EditedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("edited_at"); - - b.Property("ForwardedMessageId") - .HasColumnType("uuid") - .HasColumnName("forwarded_message_id"); - - b.Property>("MembersMentioned") - .HasColumnType("jsonb") - .HasColumnName("members_mentioned"); - - b.Property>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("Nonce") - .IsRequired() - .HasMaxLength(36) - .HasColumnType("character varying(36)") - .HasColumnName("nonce"); - - b.Property("RepliedMessageId") - .HasColumnType("uuid") - .HasColumnName("replied_message_id"); - - b.Property("SenderId") - .HasColumnType("uuid") - .HasColumnName("sender_id"); - - 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_chat_messages"); - - b.HasIndex("ChatRoomId") - .HasDatabaseName("ix_chat_messages_chat_room_id"); - - b.HasIndex("ForwardedMessageId") - .HasDatabaseName("ix_chat_messages_forwarded_message_id"); - - b.HasIndex("RepliedMessageId") - .HasDatabaseName("ix_chat_messages_replied_message_id"); - - b.HasIndex("SenderId") - .HasDatabaseName("ix_chat_messages_sender_id"); - - b.ToTable("chat_messages", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.MessageReaction", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("Attitude") - .HasColumnType("integer") - .HasColumnName("attitude"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("MessageId") - .HasColumnType("uuid") - .HasColumnName("message_id"); - - b.Property("SenderId") - .HasColumnType("uuid") - .HasColumnName("sender_id"); - - b.Property("Symbol") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("symbol"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_chat_reactions"); - - b.HasIndex("MessageId") - .HasDatabaseName("ix_chat_reactions_message_id"); - - b.HasIndex("SenderId") - .HasDatabaseName("ix_chat_reactions_sender_id"); - - b.ToTable("chat_reactions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.RealtimeCall", 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("EndedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("ended_at"); - - b.Property("ProviderName") - .HasColumnType("text") - .HasColumnName("provider_name"); - - b.Property("RoomId") - .HasColumnType("uuid") - .HasColumnName("room_id"); - - b.Property("SenderId") - .HasColumnType("uuid") - .HasColumnName("sender_id"); - - b.Property("SessionId") - .HasColumnType("text") - .HasColumnName("session_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("UpstreamConfigJson") - .HasColumnType("jsonb") - .HasColumnName("upstream"); - - b.HasKey("Id") - .HasName("pk_chat_realtime_call"); - - b.HasIndex("RoomId") - .HasDatabaseName("ix_chat_realtime_call_room_id"); - - b.HasIndex("SenderId") - .HasDatabaseName("ix_chat_realtime_call_sender_id"); - - b.ToTable("chat_realtime_call", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomApp", 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("Name") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("slug"); - - b.Property("Status") - .HasColumnType("integer") - .HasColumnName("status"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("VerifiedAs") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("verified_as"); - - b.Property("VerifiedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("verified_at"); - - b.HasKey("Id") - .HasName("pk_custom_apps"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_custom_apps_publisher_id"); - - b.ToTable("custom_apps", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomAppSecret", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AppId") - .HasColumnType("uuid") - .HasColumnName("app_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("Remarks") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("remarks"); - - b.Property("Secret") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("secret"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_custom_app_secrets"); - - b.HasIndex("AppId") - .HasDatabaseName("ix_custom_app_secrets_app_id"); - - b.ToTable("custom_app_secrets", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroup", 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.Sphere.Permission.PermissionGroupMember", 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.Sphere.Permission.PermissionNode", 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("Area") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("area"); - - 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("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", "Area", "Actor") - .HasDatabaseName("ix_permission_nodes_key_area_actor"); - - b.ToTable("permission_nodes", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.Post", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property>("Attachments") - .IsRequired() - .HasColumnType("jsonb") - .HasColumnName("attachments"); - - b.Property("Content") - .HasColumnType("text") - .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("Description") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("Downvotes") - .HasColumnType("integer") - .HasColumnName("downvotes"); - - b.Property("EditedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("edited_at"); - - b.Property("ForwardedPostId") - .HasColumnType("uuid") - .HasColumnName("forwarded_post_id"); - - b.Property("Language") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("language"); - - b.Property>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("PublishedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("published_at"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("RepliedPostId") - .HasColumnType("uuid") - .HasColumnName("replied_post_id"); - - b.Property("SearchVector") - .IsRequired() - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("tsvector") - .HasColumnName("search_vector") - .HasAnnotation("Npgsql:TsVectorConfig", "simple") - .HasAnnotation("Npgsql:TsVectorProperties", new[] { "Title", "Description", "Content" }); - - b.Property("Title") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("title"); - - b.Property("Type") - .HasColumnType("integer") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("Upvotes") - .HasColumnType("integer") - .HasColumnName("upvotes"); - - b.Property("ViewsTotal") - .HasColumnType("integer") - .HasColumnName("views_total"); - - b.Property("ViewsUnique") - .HasColumnType("integer") - .HasColumnName("views_unique"); - - b.Property("Visibility") - .HasColumnType("integer") - .HasColumnName("visibility"); - - b.HasKey("Id") - .HasName("pk_posts"); - - b.HasIndex("ForwardedPostId") - .HasDatabaseName("ix_posts_forwarded_post_id"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_posts_publisher_id"); - - b.HasIndex("RepliedPostId") - .HasDatabaseName("ix_posts_replied_post_id"); - - b.HasIndex("SearchVector") - .HasDatabaseName("ix_posts_search_vector"); - - NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex("SearchVector"), "GIN"); - - b.ToTable("posts", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostCategory", 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("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("name"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_categories"); - - b.ToTable("post_categories", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostCollection", 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("Description") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("name"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_collections"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_post_collections_publisher_id"); - - b.ToTable("post_collections", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostReaction", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("Attitude") - .HasColumnType("integer") - .HasColumnName("attitude"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("PostId") - .HasColumnType("uuid") - .HasColumnName("post_id"); - - b.Property("Symbol") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("symbol"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_reactions"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_post_reactions_account_id"); - - b.HasIndex("PostId") - .HasDatabaseName("ix_post_reactions_post_id"); - - b.ToTable("post_reactions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostTag", 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("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("name"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_tags"); - - b.ToTable("post_tags", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.Publisher", 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("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_id"); - - b.Property("Bio") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("bio"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - 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("Picture") - .HasColumnType("jsonb") - .HasColumnName("picture"); - - b.Property("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - b.Property("RealmId") - .HasColumnType("uuid") - .HasColumnName("realm_id"); - - b.Property("Type") - .HasColumnType("integer") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("Verification") - .HasColumnType("jsonb") - .HasColumnName("verification"); - - b.HasKey("Id") - .HasName("pk_publishers"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_publishers_account_id"); - - b.HasIndex("Name") - .IsUnique() - .HasDatabaseName("ix_publishers_name"); - - b.HasIndex("RealmId") - .HasDatabaseName("ix_publishers_realm_id"); - - b.ToTable("publishers", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherFeature", 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("ExpiredAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expired_at"); - - b.Property("Flag") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("flag"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_publisher_features"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_publisher_features_publisher_id"); - - b.ToTable("publisher_features", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherMember", b => - { - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_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("Role") - .HasColumnType("integer") - .HasColumnName("role"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("PublisherId", "AccountId") - .HasName("pk_publisher_members"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_publisher_members_account_id"); - - b.ToTable("publisher_members", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherSubscription", 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("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("Status") - .HasColumnType("integer") - .HasColumnName("status"); - - b.Property("Tier") - .HasColumnType("integer") - .HasColumnName("tier"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_publisher_subscriptions"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_publisher_subscriptions_account_id"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_publisher_subscriptions_publisher_id"); - - b.ToTable("publisher_subscriptions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.Realm", 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("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_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("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("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - 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("AccountId") - .HasDatabaseName("ix_realms_account_id"); - - b.HasIndex("Slug") - .IsUnique() - .HasDatabaseName("ix_realms_slug"); - - b.ToTable("realms", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.RealmMember", 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.HasIndex("AccountId") - .HasDatabaseName("ix_realm_members_account_id"); - - b.ToTable("realm_members", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.Sticker", 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("Image") - .HasColumnType("jsonb") - .HasColumnName("image"); - - b.Property("ImageId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("image_id"); - - b.Property("PackId") - .HasColumnType("uuid") - .HasColumnName("pack_id"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_stickers"); - - b.HasIndex("PackId") - .HasDatabaseName("ix_stickers_pack_id"); - - b.HasIndex("Slug") - .HasDatabaseName("ix_stickers_slug"); - - b.ToTable("stickers", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.StickerPack", 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("Description") - .IsRequired() - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("Prefix") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("prefix"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_sticker_packs"); - - b.HasIndex("Prefix") - .IsUnique() - .HasDatabaseName("ix_sticker_packs_prefix"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_sticker_packs_publisher_id"); - - b.ToTable("sticker_packs", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFile", b => - { - b.Property("Id") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .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("Description") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property>("FileMeta") - .HasColumnType("jsonb") - .HasColumnName("file_meta"); - - b.Property("HasCompression") - .HasColumnType("boolean") - .HasColumnName("has_compression"); - - b.Property("Hash") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("hash"); - - b.Property("IsMarkedRecycle") - .HasColumnType("boolean") - .HasColumnName("is_marked_recycle"); - - b.Property("MessageId") - .HasColumnType("uuid") - .HasColumnName("message_id"); - - b.Property("MimeType") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("mime_type"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("PostId") - .HasColumnType("uuid") - .HasColumnName("post_id"); - - b.Property>("SensitiveMarks") - .HasColumnType("jsonb") - .HasColumnName("sensitive_marks"); - - b.Property("Size") - .HasColumnType("bigint") - .HasColumnName("size"); - - b.Property("StorageId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("storage_id"); - - b.Property("StorageUrl") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("storage_url"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("UploadedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("uploaded_at"); - - b.Property("UploadedTo") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("uploaded_to"); - - b.Property>("UserMeta") - .HasColumnType("jsonb") - .HasColumnName("user_meta"); - - b.HasKey("Id") - .HasName("pk_files"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_files_account_id"); - - b.HasIndex("MessageId") - .HasDatabaseName("ix_files_message_id"); - - b.HasIndex("PostId") - .HasDatabaseName("ix_files_post_id"); - - b.ToTable("files", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFileReference", 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("ExpiredAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expired_at"); - - b.Property("FileId") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("file_id"); - - b.Property("ResourceId") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("resource_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("Usage") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("usage"); - - b.HasKey("Id") - .HasName("pk_file_references"); - - b.HasIndex("FileId") - .HasDatabaseName("ix_file_references_file_id"); - - b.ToTable("file_references", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Coupon", 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.Sphere.Wallet.Order", 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("ExpiredAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expired_at"); - - b.Property("IssuerAppId") - .HasColumnType("uuid") - .HasColumnName("issuer_app_id"); - - b.Property("PayeeWalletId") - .HasColumnType("uuid") - .HasColumnName("payee_wallet_id"); - - 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("IssuerAppId") - .HasDatabaseName("ix_payment_orders_issuer_app_id"); - - 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.Sphere.Wallet.Subscription", 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.ToTable("wallet_subscriptions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Transaction", 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.Sphere.Wallet.Wallet", 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.Sphere.Wallet.WalletPocket", 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("PostPostCategory", b => - { - b.Property("CategoriesId") - .HasColumnType("uuid") - .HasColumnName("categories_id"); - - b.Property("PostsId") - .HasColumnType("uuid") - .HasColumnName("posts_id"); - - b.HasKey("CategoriesId", "PostsId") - .HasName("pk_post_category_links"); - - b.HasIndex("PostsId") - .HasDatabaseName("ix_post_category_links_posts_id"); - - b.ToTable("post_category_links", (string)null); - }); - - modelBuilder.Entity("PostPostCollection", b => - { - b.Property("CollectionsId") - .HasColumnType("uuid") - .HasColumnName("collections_id"); - - b.Property("PostsId") - .HasColumnType("uuid") - .HasColumnName("posts_id"); - - b.HasKey("CollectionsId", "PostsId") - .HasName("pk_post_collection_links"); - - b.HasIndex("PostsId") - .HasDatabaseName("ix_post_collection_links_posts_id"); - - b.ToTable("post_collection_links", (string)null); - }); - - modelBuilder.Entity("PostPostTag", b => - { - b.Property("PostsId") - .HasColumnType("uuid") - .HasColumnName("posts_id"); - - b.Property("TagsId") - .HasColumnType("uuid") - .HasColumnName("tags_id"); - - b.HasKey("PostsId", "TagsId") - .HasName("pk_post_tag_links"); - - b.HasIndex("TagsId") - .HasDatabaseName("ix_post_tag_links_tags_id"); - - b.ToTable("post_tag_links", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.AccountAuthFactor", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("AuthFactors") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_auth_factors_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.AccountContact", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Contacts") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_contacts_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.ActionLog", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_action_logs_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Auth.Session", "Session") - .WithMany() - .HasForeignKey("SessionId") - .HasConstraintName("fk_action_logs_auth_sessions_session_id"); - - b.Navigation("Account"); - - b.Navigation("Session"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Badge", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Badges") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_badges_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.CheckInResult", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_check_in_results_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.MagicSpell", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .HasConstraintName("fk_magic_spells_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Notification", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_notifications_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.NotificationPushSubscription", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_notification_push_subscriptions_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Profile", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithOne("Profile") - .HasForeignKey("DysonNetwork.Sphere.Account.Profile", "AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_profiles_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Relationship", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("OutgoingRelationships") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_relationships_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Account.Account", "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.Sphere.Account.Status", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_statuses_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Challenge", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Challenges") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_challenges_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Session", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Sessions") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_sessions_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Auth.Challenge", "Challenge") - .WithMany() - .HasForeignKey("ChallengeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_sessions_auth_challenges_challenge_id"); - - b.Navigation("Account"); - - b.Navigation("Challenge"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatMember", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_members_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatRoom", "ChatRoom") - .WithMany("Members") - .HasForeignKey("ChatRoomId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_members_chat_rooms_chat_room_id"); - - b.Navigation("Account"); - - b.Navigation("ChatRoom"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatRoom", b => - { - b.HasOne("DysonNetwork.Sphere.Realm.Realm", "Realm") - .WithMany("ChatRooms") - .HasForeignKey("RealmId") - .HasConstraintName("fk_chat_rooms_realms_realm_id"); - - b.Navigation("Realm"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.Message", b => - { - b.HasOne("DysonNetwork.Sphere.Chat.ChatRoom", "ChatRoom") - .WithMany() - .HasForeignKey("ChatRoomId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_messages_chat_rooms_chat_room_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.Message", "ForwardedMessage") - .WithMany() - .HasForeignKey("ForwardedMessageId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_chat_messages_chat_messages_forwarded_message_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.Message", "RepliedMessage") - .WithMany() - .HasForeignKey("RepliedMessageId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_chat_messages_chat_messages_replied_message_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatMember", "Sender") - .WithMany() - .HasForeignKey("SenderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_messages_chat_members_sender_id"); - - b.Navigation("ChatRoom"); - - b.Navigation("ForwardedMessage"); - - b.Navigation("RepliedMessage"); - - b.Navigation("Sender"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.MessageReaction", b => - { - b.HasOne("DysonNetwork.Sphere.Chat.Message", "Message") - .WithMany("Reactions") - .HasForeignKey("MessageId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_reactions_chat_messages_message_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatMember", "Sender") - .WithMany() - .HasForeignKey("SenderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_reactions_chat_members_sender_id"); - - b.Navigation("Message"); - - b.Navigation("Sender"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.RealtimeCall", b => - { - b.HasOne("DysonNetwork.Sphere.Chat.ChatRoom", "Room") - .WithMany() - .HasForeignKey("RoomId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_realtime_call_chat_rooms_room_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatMember", "Sender") - .WithMany() - .HasForeignKey("SenderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_realtime_call_chat_members_sender_id"); - - b.Navigation("Room"); - - b.Navigation("Sender"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomApp", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Developer") - .WithMany() - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_custom_apps_publishers_publisher_id"); - - b.Navigation("Developer"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomAppSecret", b => - { - b.HasOne("DysonNetwork.Sphere.Developer.CustomApp", "App") - .WithMany() - .HasForeignKey("AppId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_custom_app_secrets_custom_apps_app_id"); - - b.Navigation("App"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroupMember", b => - { - b.HasOne("DysonNetwork.Sphere.Permission.PermissionGroup", "Group") - .WithMany("Members") - .HasForeignKey("GroupId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_permission_group_members_permission_groups_group_id"); - - b.Navigation("Group"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionNode", b => - { - b.HasOne("DysonNetwork.Sphere.Permission.PermissionGroup", "Group") - .WithMany("Nodes") - .HasForeignKey("GroupId") - .HasConstraintName("fk_permission_nodes_permission_groups_group_id"); - - b.Navigation("Group"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.Post", b => - { - b.HasOne("DysonNetwork.Sphere.Post.Post", "ForwardedPost") - .WithMany() - .HasForeignKey("ForwardedPostId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_posts_posts_forwarded_post_id"); - - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Posts") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_posts_publishers_publisher_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", "RepliedPost") - .WithMany() - .HasForeignKey("RepliedPostId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_posts_posts_replied_post_id"); - - b.Navigation("ForwardedPost"); - - b.Navigation("Publisher"); - - b.Navigation("RepliedPost"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostCollection", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Collections") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_collections_publishers_publisher_id"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostReaction", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_reactions_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", "Post") - .WithMany("Reactions") - .HasForeignKey("PostId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_reactions_posts_post_id"); - - b.Navigation("Account"); - - b.Navigation("Post"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.Publisher", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .HasConstraintName("fk_publishers_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Realm.Realm", "Realm") - .WithMany() - .HasForeignKey("RealmId") - .HasConstraintName("fk_publishers_realms_realm_id"); - - b.Navigation("Account"); - - b.Navigation("Realm"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherFeature", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany() - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_features_publishers_publisher_id"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherMember", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_members_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Members") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_members_publishers_publisher_id"); - - b.Navigation("Account"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherSubscription", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_subscriptions_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Subscriptions") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_subscriptions_publishers_publisher_id"); - - b.Navigation("Account"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.Realm", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_realms_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.RealmMember", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_realm_members_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Realm.Realm", "Realm") - .WithMany("Members") - .HasForeignKey("RealmId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_realm_members_realms_realm_id"); - - b.Navigation("Account"); - - b.Navigation("Realm"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.Sticker", b => - { - b.HasOne("DysonNetwork.Sphere.Sticker.StickerPack", "Pack") - .WithMany() - .HasForeignKey("PackId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_stickers_sticker_packs_pack_id"); - - b.Navigation("Pack"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.StickerPack", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany() - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_sticker_packs_publishers_publisher_id"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFile", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_files_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.Message", null) - .WithMany("OutdatedAttachments") - .HasForeignKey("MessageId") - .HasConstraintName("fk_files_chat_messages_message_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany("OutdatedAttachments") - .HasForeignKey("PostId") - .HasConstraintName("fk_files_posts_post_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFileReference", b => - { - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "File") - .WithMany() - .HasForeignKey("FileId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_file_references_files_file_id"); - - b.Navigation("File"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Order", b => - { - b.HasOne("DysonNetwork.Sphere.Developer.CustomApp", "IssuerApp") - .WithMany() - .HasForeignKey("IssuerAppId") - .HasConstraintName("fk_payment_orders_custom_apps_issuer_app_id"); - - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "PayeeWallet") - .WithMany() - .HasForeignKey("PayeeWalletId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_payment_orders_wallets_payee_wallet_id"); - - b.HasOne("DysonNetwork.Sphere.Wallet.Transaction", "Transaction") - .WithMany() - .HasForeignKey("TransactionId") - .HasConstraintName("fk_payment_orders_payment_transactions_transaction_id"); - - b.Navigation("IssuerApp"); - - b.Navigation("PayeeWallet"); - - b.Navigation("Transaction"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Subscription", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Subscriptions") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_wallet_subscriptions_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Wallet.Coupon", "Coupon") - .WithMany() - .HasForeignKey("CouponId") - .HasConstraintName("fk_wallet_subscriptions_wallet_coupons_coupon_id"); - - b.Navigation("Account"); - - b.Navigation("Coupon"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Transaction", b => - { - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "PayeeWallet") - .WithMany() - .HasForeignKey("PayeeWalletId") - .HasConstraintName("fk_payment_transactions_wallets_payee_wallet_id"); - - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "PayerWallet") - .WithMany() - .HasForeignKey("PayerWalletId") - .HasConstraintName("fk_payment_transactions_wallets_payer_wallet_id"); - - b.Navigation("PayeeWallet"); - - b.Navigation("PayerWallet"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Wallet", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_wallets_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.WalletPocket", b => - { - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "Wallet") - .WithMany("Pockets") - .HasForeignKey("WalletId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_wallet_pockets_wallets_wallet_id"); - - b.Navigation("Wallet"); - }); - - modelBuilder.Entity("PostPostCategory", b => - { - b.HasOne("DysonNetwork.Sphere.Post.PostCategory", null) - .WithMany() - .HasForeignKey("CategoriesId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_category_links_post_categories_categories_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany() - .HasForeignKey("PostsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_category_links_posts_posts_id"); - }); - - modelBuilder.Entity("PostPostCollection", b => - { - b.HasOne("DysonNetwork.Sphere.Post.PostCollection", null) - .WithMany() - .HasForeignKey("CollectionsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_collection_links_post_collections_collections_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany() - .HasForeignKey("PostsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_collection_links_posts_posts_id"); - }); - - modelBuilder.Entity("PostPostTag", b => - { - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany() - .HasForeignKey("PostsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_tag_links_posts_posts_id"); - - b.HasOne("DysonNetwork.Sphere.Post.PostTag", null) - .WithMany() - .HasForeignKey("TagsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_tag_links_post_tags_tags_id"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Account", b => - { - b.Navigation("AuthFactors"); - - b.Navigation("Badges"); - - b.Navigation("Challenges"); - - b.Navigation("Contacts"); - - b.Navigation("IncomingRelationships"); - - b.Navigation("OutgoingRelationships"); - - b.Navigation("Profile") - .IsRequired(); - - b.Navigation("Sessions"); - - b.Navigation("Subscriptions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatRoom", b => - { - b.Navigation("Members"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.Message", b => - { - b.Navigation("OutdatedAttachments"); - - b.Navigation("Reactions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroup", b => - { - b.Navigation("Members"); - - b.Navigation("Nodes"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.Post", b => - { - b.Navigation("OutdatedAttachments"); - - b.Navigation("Reactions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.Publisher", b => - { - b.Navigation("Collections"); - - b.Navigation("Members"); - - b.Navigation("Posts"); - - b.Navigation("Subscriptions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.Realm", b => - { - b.Navigation("ChatRooms"); - - b.Navigation("Members"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Wallet", b => - { - b.Navigation("Pockets"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/DysonNetwork.Sphere/Migrations/20250611165902_BetterRecyclingFilesAndWalletSubscriptions.cs b/DysonNetwork.Sphere/Migrations/20250611165902_BetterRecyclingFilesAndWalletSubscriptions.cs deleted file mode 100644 index 2c411b1..0000000 --- a/DysonNetwork.Sphere/Migrations/20250611165902_BetterRecyclingFilesAndWalletSubscriptions.cs +++ /dev/null @@ -1,143 +0,0 @@ -using System; -using DysonNetwork.Sphere.Wallet; -using Microsoft.EntityFrameworkCore.Migrations; -using NodaTime; - -#nullable disable - -namespace DysonNetwork.Sphere.Migrations -{ - /// - public partial class BetterRecyclingFilesAndWalletSubscriptions : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "is_marked_recycle", - table: "files", - type: "boolean", - nullable: false, - defaultValue: false); - - migrationBuilder.AddColumn( - name: "location", - table: "account_profiles", - type: "character varying(1024)", - maxLength: 1024, - nullable: true); - - migrationBuilder.AddColumn( - name: "stellar_membership", - table: "account_profiles", - type: "jsonb", - nullable: true); - - migrationBuilder.AddColumn( - name: "time_zone", - table: "account_profiles", - type: "character varying(1024)", - maxLength: 1024, - nullable: true); - - migrationBuilder.CreateTable( - name: "wallet_coupons", - columns: table => new - { - id = table.Column(type: "uuid", nullable: false), - identifier = table.Column(type: "character varying(4096)", maxLength: 4096, nullable: true), - code = table.Column(type: "character varying(1024)", maxLength: 1024, nullable: true), - affected_at = table.Column(type: "timestamp with time zone", nullable: true), - expired_at = table.Column(type: "timestamp with time zone", nullable: true), - discount_amount = table.Column(type: "numeric", nullable: true), - discount_rate = table.Column(type: "double precision", nullable: true), - max_usage = table.Column(type: "integer", nullable: true), - created_at = table.Column(type: "timestamp with time zone", nullable: false), - updated_at = table.Column(type: "timestamp with time zone", nullable: false), - deleted_at = table.Column(type: "timestamp with time zone", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("pk_wallet_coupons", x => x.id); - }); - - migrationBuilder.CreateTable( - name: "wallet_subscriptions", - columns: table => new - { - id = table.Column(type: "uuid", nullable: false), - begun_at = table.Column(type: "timestamp with time zone", nullable: false), - ended_at = table.Column(type: "timestamp with time zone", nullable: true), - identifier = table.Column(type: "character varying(4096)", maxLength: 4096, nullable: false), - is_active = table.Column(type: "boolean", nullable: false), - is_free_trial = table.Column(type: "boolean", nullable: false), - status = table.Column(type: "integer", nullable: false), - payment_method = table.Column(type: "character varying(4096)", maxLength: 4096, nullable: false), - payment_details = table.Column(type: "jsonb", nullable: false), - base_price = table.Column(type: "numeric", nullable: false), - coupon_id = table.Column(type: "uuid", nullable: true), - renewal_at = table.Column(type: "timestamp with time zone", nullable: true), - account_id = table.Column(type: "uuid", nullable: false), - created_at = table.Column(type: "timestamp with time zone", nullable: false), - updated_at = table.Column(type: "timestamp with time zone", nullable: false), - deleted_at = table.Column(type: "timestamp with time zone", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("pk_wallet_subscriptions", x => x.id); - table.ForeignKey( - name: "fk_wallet_subscriptions_accounts_account_id", - column: x => x.account_id, - principalTable: "accounts", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "fk_wallet_subscriptions_wallet_coupons_coupon_id", - column: x => x.coupon_id, - principalTable: "wallet_coupons", - principalColumn: "id"); - }); - - migrationBuilder.CreateIndex( - name: "ix_wallet_subscriptions_account_id", - table: "wallet_subscriptions", - column: "account_id"); - - migrationBuilder.CreateIndex( - name: "ix_wallet_subscriptions_coupon_id", - table: "wallet_subscriptions", - column: "coupon_id"); - - migrationBuilder.CreateIndex( - name: "ix_wallet_subscriptions_identifier", - table: "wallet_subscriptions", - column: "identifier"); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "wallet_subscriptions"); - - migrationBuilder.DropTable( - name: "wallet_coupons"); - - migrationBuilder.DropColumn( - name: "is_marked_recycle", - table: "files"); - - migrationBuilder.DropColumn( - name: "location", - table: "account_profiles"); - - migrationBuilder.DropColumn( - name: "stellar_membership", - table: "account_profiles"); - - migrationBuilder.DropColumn( - name: "time_zone", - table: "account_profiles"); - } - } -} diff --git a/DysonNetwork.Sphere/Migrations/20250615083256_AddAccountConnection.Designer.cs b/DysonNetwork.Sphere/Migrations/20250615083256_AddAccountConnection.Designer.cs deleted file mode 100644 index 0fe5183..0000000 --- a/DysonNetwork.Sphere/Migrations/20250615083256_AddAccountConnection.Designer.cs +++ /dev/null @@ -1,3622 +0,0 @@ -// -using System; -using System.Collections.Generic; -using System.Text.Json; -using DysonNetwork.Sphere; -using DysonNetwork.Sphere.Account; -using DysonNetwork.Sphere.Chat; -using DysonNetwork.Sphere.Storage; -using DysonNetwork.Sphere.Wallet; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using NetTopologySuite.Geometries; -using NodaTime; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; -using NpgsqlTypes; - -#nullable disable - -namespace DysonNetwork.Sphere.Migrations -{ - [DbContext(typeof(AppDatabase))] - [Migration("20250615083256_AddAccountConnection")] - partial class AddAccountConnection - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "9.0.3") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "postgis"); - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Account", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("ActivatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("activated_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("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("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.Sphere.Account.AccountAuthFactor", 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.Sphere.Account.AccountConnection", 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("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.Sphere.Account.AccountContact", 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("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.Sphere.Account.ActionLog", 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("geometry") - .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.HasIndex("SessionId") - .HasDatabaseName("ix_action_logs_session_id"); - - b.ToTable("action_logs", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Badge", 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.Sphere.Account.CheckInResult", 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("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.Sphere.Account.MagicSpell", 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.Sphere.Account.Notification", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("Content") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .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>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("Priority") - .HasColumnType("integer") - .HasColumnName("priority"); - - b.Property("Subtitle") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("subtitle"); - - b.Property("Title") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("title"); - - b.Property("Topic") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("topic"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("ViewedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("viewed_at"); - - b.HasKey("Id") - .HasName("pk_notifications"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_notifications_account_id"); - - b.ToTable("notifications", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.NotificationPushSubscription", 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(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("device_id"); - - b.Property("DeviceToken") - .IsRequired() - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("device_token"); - - b.Property("LastUsedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_used_at"); - - b.Property("Provider") - .HasColumnType("integer") - .HasColumnName("provider"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_notification_push_subscriptions"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_notification_push_subscriptions_account_id"); - - b.HasIndex("DeviceToken", "DeviceId", "AccountId") - .IsUnique() - .HasDatabaseName("ix_notification_push_subscriptions_device_token_device_id_acco"); - - b.ToTable("notification_push_subscriptions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Profile", 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("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_id"); - - 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("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("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - b.Property("Pronouns") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("pronouns"); - - b.Property("StellarMembership") - .HasColumnType("jsonb") - .HasColumnName("stellar_membership"); - - 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("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.Sphere.Account.Relationship", 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.Sphere.Account.Status", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - 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("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("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.Sphere.Auth.Challenge", 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") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("device_id"); - - 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("geometry") - .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("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_challenges"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_auth_challenges_account_id"); - - b.ToTable("auth_challenges", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Session", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("ChallengeId") - .HasColumnType("uuid") - .HasColumnName("challenge_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("Label") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("label"); - - b.Property("LastGrantedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_granted_at"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - 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.ToTable("auth_sessions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatMember", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("BreakUntil") - .HasColumnType("timestamp with time zone") - .HasColumnName("break_until"); - - b.Property("ChatRoomId") - .HasColumnType("uuid") - .HasColumnName("chat_room_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("IsBot") - .HasColumnType("boolean") - .HasColumnName("is_bot"); - - b.Property("JoinedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("joined_at"); - - b.Property("LastReadAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_read_at"); - - b.Property("LeaveAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("leave_at"); - - b.Property("Nick") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("nick"); - - b.Property("Notify") - .HasColumnType("integer") - .HasColumnName("notify"); - - b.Property("Role") - .HasColumnType("integer") - .HasColumnName("role"); - - b.Property("TimeoutCause") - .HasColumnType("jsonb") - .HasColumnName("timeout_cause"); - - b.Property("TimeoutUntil") - .HasColumnType("timestamp with time zone") - .HasColumnName("timeout_until"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_chat_members"); - - b.HasAlternateKey("ChatRoomId", "AccountId") - .HasName("ak_chat_members_chat_room_id_account_id"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_chat_members_account_id"); - - b.ToTable("chat_members", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatRoom", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("Background") - .HasColumnType("jsonb") - .HasColumnName("background"); - - b.Property("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_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("Description") - .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") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("Picture") - .HasColumnType("jsonb") - .HasColumnName("picture"); - - b.Property("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - b.Property("RealmId") - .HasColumnType("uuid") - .HasColumnName("realm_id"); - - b.Property("Type") - .HasColumnType("integer") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_chat_rooms"); - - b.HasIndex("RealmId") - .HasDatabaseName("ix_chat_rooms_realm_id"); - - b.ToTable("chat_rooms", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.Message", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property>("Attachments") - .IsRequired() - .HasColumnType("jsonb") - .HasColumnName("attachments"); - - b.Property("ChatRoomId") - .HasColumnType("uuid") - .HasColumnName("chat_room_id"); - - b.Property("Content") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .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("EditedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("edited_at"); - - b.Property("ForwardedMessageId") - .HasColumnType("uuid") - .HasColumnName("forwarded_message_id"); - - b.Property>("MembersMentioned") - .HasColumnType("jsonb") - .HasColumnName("members_mentioned"); - - b.Property>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("Nonce") - .IsRequired() - .HasMaxLength(36) - .HasColumnType("character varying(36)") - .HasColumnName("nonce"); - - b.Property("RepliedMessageId") - .HasColumnType("uuid") - .HasColumnName("replied_message_id"); - - b.Property("SenderId") - .HasColumnType("uuid") - .HasColumnName("sender_id"); - - 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_chat_messages"); - - b.HasIndex("ChatRoomId") - .HasDatabaseName("ix_chat_messages_chat_room_id"); - - b.HasIndex("ForwardedMessageId") - .HasDatabaseName("ix_chat_messages_forwarded_message_id"); - - b.HasIndex("RepliedMessageId") - .HasDatabaseName("ix_chat_messages_replied_message_id"); - - b.HasIndex("SenderId") - .HasDatabaseName("ix_chat_messages_sender_id"); - - b.ToTable("chat_messages", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.MessageReaction", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("Attitude") - .HasColumnType("integer") - .HasColumnName("attitude"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("MessageId") - .HasColumnType("uuid") - .HasColumnName("message_id"); - - b.Property("SenderId") - .HasColumnType("uuid") - .HasColumnName("sender_id"); - - b.Property("Symbol") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("symbol"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_chat_reactions"); - - b.HasIndex("MessageId") - .HasDatabaseName("ix_chat_reactions_message_id"); - - b.HasIndex("SenderId") - .HasDatabaseName("ix_chat_reactions_sender_id"); - - b.ToTable("chat_reactions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.RealtimeCall", 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("EndedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("ended_at"); - - b.Property("ProviderName") - .HasColumnType("text") - .HasColumnName("provider_name"); - - b.Property("RoomId") - .HasColumnType("uuid") - .HasColumnName("room_id"); - - b.Property("SenderId") - .HasColumnType("uuid") - .HasColumnName("sender_id"); - - b.Property("SessionId") - .HasColumnType("text") - .HasColumnName("session_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("UpstreamConfigJson") - .HasColumnType("jsonb") - .HasColumnName("upstream"); - - b.HasKey("Id") - .HasName("pk_chat_realtime_call"); - - b.HasIndex("RoomId") - .HasDatabaseName("ix_chat_realtime_call_room_id"); - - b.HasIndex("SenderId") - .HasDatabaseName("ix_chat_realtime_call_sender_id"); - - b.ToTable("chat_realtime_call", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomApp", 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("Name") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("slug"); - - b.Property("Status") - .HasColumnType("integer") - .HasColumnName("status"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("VerifiedAs") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("verified_as"); - - b.Property("VerifiedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("verified_at"); - - b.HasKey("Id") - .HasName("pk_custom_apps"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_custom_apps_publisher_id"); - - b.ToTable("custom_apps", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomAppSecret", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AppId") - .HasColumnType("uuid") - .HasColumnName("app_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("Remarks") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("remarks"); - - b.Property("Secret") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("secret"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_custom_app_secrets"); - - b.HasIndex("AppId") - .HasDatabaseName("ix_custom_app_secrets_app_id"); - - b.ToTable("custom_app_secrets", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroup", 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.Sphere.Permission.PermissionGroupMember", 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.Sphere.Permission.PermissionNode", 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("Area") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("area"); - - 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("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", "Area", "Actor") - .HasDatabaseName("ix_permission_nodes_key_area_actor"); - - b.ToTable("permission_nodes", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.Post", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property>("Attachments") - .IsRequired() - .HasColumnType("jsonb") - .HasColumnName("attachments"); - - b.Property("Content") - .HasColumnType("text") - .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("Description") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("Downvotes") - .HasColumnType("integer") - .HasColumnName("downvotes"); - - b.Property("EditedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("edited_at"); - - b.Property("ForwardedPostId") - .HasColumnType("uuid") - .HasColumnName("forwarded_post_id"); - - b.Property("Language") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("language"); - - b.Property>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("PublishedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("published_at"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("RepliedPostId") - .HasColumnType("uuid") - .HasColumnName("replied_post_id"); - - b.Property("SearchVector") - .IsRequired() - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("tsvector") - .HasColumnName("search_vector") - .HasAnnotation("Npgsql:TsVectorConfig", "simple") - .HasAnnotation("Npgsql:TsVectorProperties", new[] { "Title", "Description", "Content" }); - - b.Property("Title") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("title"); - - b.Property("Type") - .HasColumnType("integer") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("Upvotes") - .HasColumnType("integer") - .HasColumnName("upvotes"); - - b.Property("ViewsTotal") - .HasColumnType("integer") - .HasColumnName("views_total"); - - b.Property("ViewsUnique") - .HasColumnType("integer") - .HasColumnName("views_unique"); - - b.Property("Visibility") - .HasColumnType("integer") - .HasColumnName("visibility"); - - b.HasKey("Id") - .HasName("pk_posts"); - - b.HasIndex("ForwardedPostId") - .HasDatabaseName("ix_posts_forwarded_post_id"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_posts_publisher_id"); - - b.HasIndex("RepliedPostId") - .HasDatabaseName("ix_posts_replied_post_id"); - - b.HasIndex("SearchVector") - .HasDatabaseName("ix_posts_search_vector"); - - NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex("SearchVector"), "GIN"); - - b.ToTable("posts", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostCategory", 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("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("name"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_categories"); - - b.ToTable("post_categories", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostCollection", 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("Description") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("name"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_collections"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_post_collections_publisher_id"); - - b.ToTable("post_collections", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostReaction", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("Attitude") - .HasColumnType("integer") - .HasColumnName("attitude"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("PostId") - .HasColumnType("uuid") - .HasColumnName("post_id"); - - b.Property("Symbol") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("symbol"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_reactions"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_post_reactions_account_id"); - - b.HasIndex("PostId") - .HasDatabaseName("ix_post_reactions_post_id"); - - b.ToTable("post_reactions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostTag", 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("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("name"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_tags"); - - b.ToTable("post_tags", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.Publisher", 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("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_id"); - - b.Property("Bio") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("bio"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - 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("Picture") - .HasColumnType("jsonb") - .HasColumnName("picture"); - - b.Property("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - b.Property("RealmId") - .HasColumnType("uuid") - .HasColumnName("realm_id"); - - b.Property("Type") - .HasColumnType("integer") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("Verification") - .HasColumnType("jsonb") - .HasColumnName("verification"); - - b.HasKey("Id") - .HasName("pk_publishers"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_publishers_account_id"); - - b.HasIndex("Name") - .IsUnique() - .HasDatabaseName("ix_publishers_name"); - - b.HasIndex("RealmId") - .HasDatabaseName("ix_publishers_realm_id"); - - b.ToTable("publishers", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherFeature", 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("ExpiredAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expired_at"); - - b.Property("Flag") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("flag"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_publisher_features"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_publisher_features_publisher_id"); - - b.ToTable("publisher_features", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherMember", b => - { - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_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("Role") - .HasColumnType("integer") - .HasColumnName("role"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("PublisherId", "AccountId") - .HasName("pk_publisher_members"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_publisher_members_account_id"); - - b.ToTable("publisher_members", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherSubscription", 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("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("Status") - .HasColumnType("integer") - .HasColumnName("status"); - - b.Property("Tier") - .HasColumnType("integer") - .HasColumnName("tier"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_publisher_subscriptions"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_publisher_subscriptions_account_id"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_publisher_subscriptions_publisher_id"); - - b.ToTable("publisher_subscriptions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.Realm", 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("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_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("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("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - 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("AccountId") - .HasDatabaseName("ix_realms_account_id"); - - b.HasIndex("Slug") - .IsUnique() - .HasDatabaseName("ix_realms_slug"); - - b.ToTable("realms", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.RealmMember", 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.HasIndex("AccountId") - .HasDatabaseName("ix_realm_members_account_id"); - - b.ToTable("realm_members", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.Sticker", 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("Image") - .HasColumnType("jsonb") - .HasColumnName("image"); - - b.Property("ImageId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("image_id"); - - b.Property("PackId") - .HasColumnType("uuid") - .HasColumnName("pack_id"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_stickers"); - - b.HasIndex("PackId") - .HasDatabaseName("ix_stickers_pack_id"); - - b.HasIndex("Slug") - .HasDatabaseName("ix_stickers_slug"); - - b.ToTable("stickers", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.StickerPack", 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("Description") - .IsRequired() - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("Prefix") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("prefix"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_sticker_packs"); - - b.HasIndex("Prefix") - .IsUnique() - .HasDatabaseName("ix_sticker_packs_prefix"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_sticker_packs_publisher_id"); - - b.ToTable("sticker_packs", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFile", b => - { - b.Property("Id") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .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("Description") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property>("FileMeta") - .HasColumnType("jsonb") - .HasColumnName("file_meta"); - - b.Property("HasCompression") - .HasColumnType("boolean") - .HasColumnName("has_compression"); - - b.Property("Hash") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("hash"); - - b.Property("IsMarkedRecycle") - .HasColumnType("boolean") - .HasColumnName("is_marked_recycle"); - - b.Property("MessageId") - .HasColumnType("uuid") - .HasColumnName("message_id"); - - b.Property("MimeType") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("mime_type"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("PostId") - .HasColumnType("uuid") - .HasColumnName("post_id"); - - b.Property>("SensitiveMarks") - .HasColumnType("jsonb") - .HasColumnName("sensitive_marks"); - - b.Property("Size") - .HasColumnType("bigint") - .HasColumnName("size"); - - b.Property("StorageId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("storage_id"); - - b.Property("StorageUrl") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("storage_url"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("UploadedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("uploaded_at"); - - b.Property("UploadedTo") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("uploaded_to"); - - b.Property>("UserMeta") - .HasColumnType("jsonb") - .HasColumnName("user_meta"); - - b.HasKey("Id") - .HasName("pk_files"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_files_account_id"); - - b.HasIndex("MessageId") - .HasDatabaseName("ix_files_message_id"); - - b.HasIndex("PostId") - .HasDatabaseName("ix_files_post_id"); - - b.ToTable("files", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFileReference", 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("ExpiredAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expired_at"); - - b.Property("FileId") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("file_id"); - - b.Property("ResourceId") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("resource_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("Usage") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("usage"); - - b.HasKey("Id") - .HasName("pk_file_references"); - - b.HasIndex("FileId") - .HasDatabaseName("ix_file_references_file_id"); - - b.ToTable("file_references", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Coupon", 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.Sphere.Wallet.Order", 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("ExpiredAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expired_at"); - - b.Property("IssuerAppId") - .HasColumnType("uuid") - .HasColumnName("issuer_app_id"); - - b.Property("PayeeWalletId") - .HasColumnType("uuid") - .HasColumnName("payee_wallet_id"); - - 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("IssuerAppId") - .HasDatabaseName("ix_payment_orders_issuer_app_id"); - - 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.Sphere.Wallet.Subscription", 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.ToTable("wallet_subscriptions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Transaction", 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.Sphere.Wallet.Wallet", 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.Sphere.Wallet.WalletPocket", 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("PostPostCategory", b => - { - b.Property("CategoriesId") - .HasColumnType("uuid") - .HasColumnName("categories_id"); - - b.Property("PostsId") - .HasColumnType("uuid") - .HasColumnName("posts_id"); - - b.HasKey("CategoriesId", "PostsId") - .HasName("pk_post_category_links"); - - b.HasIndex("PostsId") - .HasDatabaseName("ix_post_category_links_posts_id"); - - b.ToTable("post_category_links", (string)null); - }); - - modelBuilder.Entity("PostPostCollection", b => - { - b.Property("CollectionsId") - .HasColumnType("uuid") - .HasColumnName("collections_id"); - - b.Property("PostsId") - .HasColumnType("uuid") - .HasColumnName("posts_id"); - - b.HasKey("CollectionsId", "PostsId") - .HasName("pk_post_collection_links"); - - b.HasIndex("PostsId") - .HasDatabaseName("ix_post_collection_links_posts_id"); - - b.ToTable("post_collection_links", (string)null); - }); - - modelBuilder.Entity("PostPostTag", b => - { - b.Property("PostsId") - .HasColumnType("uuid") - .HasColumnName("posts_id"); - - b.Property("TagsId") - .HasColumnType("uuid") - .HasColumnName("tags_id"); - - b.HasKey("PostsId", "TagsId") - .HasName("pk_post_tag_links"); - - b.HasIndex("TagsId") - .HasDatabaseName("ix_post_tag_links_tags_id"); - - b.ToTable("post_tag_links", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.AccountAuthFactor", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("AuthFactors") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_auth_factors_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.AccountConnection", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Connections") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_connections_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.AccountContact", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Contacts") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_contacts_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.ActionLog", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_action_logs_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Auth.Session", "Session") - .WithMany() - .HasForeignKey("SessionId") - .HasConstraintName("fk_action_logs_auth_sessions_session_id"); - - b.Navigation("Account"); - - b.Navigation("Session"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Badge", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Badges") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_badges_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.CheckInResult", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_check_in_results_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.MagicSpell", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .HasConstraintName("fk_magic_spells_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Notification", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_notifications_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.NotificationPushSubscription", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_notification_push_subscriptions_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Profile", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithOne("Profile") - .HasForeignKey("DysonNetwork.Sphere.Account.Profile", "AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_profiles_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Relationship", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("OutgoingRelationships") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_relationships_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Account.Account", "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.Sphere.Account.Status", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_statuses_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Challenge", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Challenges") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_challenges_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Session", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Sessions") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_sessions_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Auth.Challenge", "Challenge") - .WithMany() - .HasForeignKey("ChallengeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_sessions_auth_challenges_challenge_id"); - - b.Navigation("Account"); - - b.Navigation("Challenge"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatMember", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_members_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatRoom", "ChatRoom") - .WithMany("Members") - .HasForeignKey("ChatRoomId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_members_chat_rooms_chat_room_id"); - - b.Navigation("Account"); - - b.Navigation("ChatRoom"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatRoom", b => - { - b.HasOne("DysonNetwork.Sphere.Realm.Realm", "Realm") - .WithMany("ChatRooms") - .HasForeignKey("RealmId") - .HasConstraintName("fk_chat_rooms_realms_realm_id"); - - b.Navigation("Realm"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.Message", b => - { - b.HasOne("DysonNetwork.Sphere.Chat.ChatRoom", "ChatRoom") - .WithMany() - .HasForeignKey("ChatRoomId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_messages_chat_rooms_chat_room_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.Message", "ForwardedMessage") - .WithMany() - .HasForeignKey("ForwardedMessageId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_chat_messages_chat_messages_forwarded_message_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.Message", "RepliedMessage") - .WithMany() - .HasForeignKey("RepliedMessageId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_chat_messages_chat_messages_replied_message_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatMember", "Sender") - .WithMany() - .HasForeignKey("SenderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_messages_chat_members_sender_id"); - - b.Navigation("ChatRoom"); - - b.Navigation("ForwardedMessage"); - - b.Navigation("RepliedMessage"); - - b.Navigation("Sender"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.MessageReaction", b => - { - b.HasOne("DysonNetwork.Sphere.Chat.Message", "Message") - .WithMany("Reactions") - .HasForeignKey("MessageId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_reactions_chat_messages_message_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatMember", "Sender") - .WithMany() - .HasForeignKey("SenderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_reactions_chat_members_sender_id"); - - b.Navigation("Message"); - - b.Navigation("Sender"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.RealtimeCall", b => - { - b.HasOne("DysonNetwork.Sphere.Chat.ChatRoom", "Room") - .WithMany() - .HasForeignKey("RoomId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_realtime_call_chat_rooms_room_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatMember", "Sender") - .WithMany() - .HasForeignKey("SenderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_realtime_call_chat_members_sender_id"); - - b.Navigation("Room"); - - b.Navigation("Sender"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomApp", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Developer") - .WithMany() - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_custom_apps_publishers_publisher_id"); - - b.Navigation("Developer"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomAppSecret", b => - { - b.HasOne("DysonNetwork.Sphere.Developer.CustomApp", "App") - .WithMany() - .HasForeignKey("AppId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_custom_app_secrets_custom_apps_app_id"); - - b.Navigation("App"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroupMember", b => - { - b.HasOne("DysonNetwork.Sphere.Permission.PermissionGroup", "Group") - .WithMany("Members") - .HasForeignKey("GroupId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_permission_group_members_permission_groups_group_id"); - - b.Navigation("Group"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionNode", b => - { - b.HasOne("DysonNetwork.Sphere.Permission.PermissionGroup", "Group") - .WithMany("Nodes") - .HasForeignKey("GroupId") - .HasConstraintName("fk_permission_nodes_permission_groups_group_id"); - - b.Navigation("Group"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.Post", b => - { - b.HasOne("DysonNetwork.Sphere.Post.Post", "ForwardedPost") - .WithMany() - .HasForeignKey("ForwardedPostId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_posts_posts_forwarded_post_id"); - - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Posts") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_posts_publishers_publisher_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", "RepliedPost") - .WithMany() - .HasForeignKey("RepliedPostId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_posts_posts_replied_post_id"); - - b.Navigation("ForwardedPost"); - - b.Navigation("Publisher"); - - b.Navigation("RepliedPost"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostCollection", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Collections") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_collections_publishers_publisher_id"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostReaction", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_reactions_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", "Post") - .WithMany("Reactions") - .HasForeignKey("PostId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_reactions_posts_post_id"); - - b.Navigation("Account"); - - b.Navigation("Post"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.Publisher", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .HasConstraintName("fk_publishers_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Realm.Realm", "Realm") - .WithMany() - .HasForeignKey("RealmId") - .HasConstraintName("fk_publishers_realms_realm_id"); - - b.Navigation("Account"); - - b.Navigation("Realm"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherFeature", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany() - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_features_publishers_publisher_id"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherMember", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_members_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Members") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_members_publishers_publisher_id"); - - b.Navigation("Account"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherSubscription", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_subscriptions_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Subscriptions") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_subscriptions_publishers_publisher_id"); - - b.Navigation("Account"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.Realm", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_realms_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.RealmMember", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_realm_members_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Realm.Realm", "Realm") - .WithMany("Members") - .HasForeignKey("RealmId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_realm_members_realms_realm_id"); - - b.Navigation("Account"); - - b.Navigation("Realm"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.Sticker", b => - { - b.HasOne("DysonNetwork.Sphere.Sticker.StickerPack", "Pack") - .WithMany() - .HasForeignKey("PackId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_stickers_sticker_packs_pack_id"); - - b.Navigation("Pack"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.StickerPack", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany() - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_sticker_packs_publishers_publisher_id"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFile", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_files_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.Message", null) - .WithMany("OutdatedAttachments") - .HasForeignKey("MessageId") - .HasConstraintName("fk_files_chat_messages_message_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany("OutdatedAttachments") - .HasForeignKey("PostId") - .HasConstraintName("fk_files_posts_post_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFileReference", b => - { - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "File") - .WithMany() - .HasForeignKey("FileId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_file_references_files_file_id"); - - b.Navigation("File"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Order", b => - { - b.HasOne("DysonNetwork.Sphere.Developer.CustomApp", "IssuerApp") - .WithMany() - .HasForeignKey("IssuerAppId") - .HasConstraintName("fk_payment_orders_custom_apps_issuer_app_id"); - - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "PayeeWallet") - .WithMany() - .HasForeignKey("PayeeWalletId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_payment_orders_wallets_payee_wallet_id"); - - b.HasOne("DysonNetwork.Sphere.Wallet.Transaction", "Transaction") - .WithMany() - .HasForeignKey("TransactionId") - .HasConstraintName("fk_payment_orders_payment_transactions_transaction_id"); - - b.Navigation("IssuerApp"); - - b.Navigation("PayeeWallet"); - - b.Navigation("Transaction"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Subscription", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Subscriptions") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_wallet_subscriptions_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Wallet.Coupon", "Coupon") - .WithMany() - .HasForeignKey("CouponId") - .HasConstraintName("fk_wallet_subscriptions_wallet_coupons_coupon_id"); - - b.Navigation("Account"); - - b.Navigation("Coupon"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Transaction", b => - { - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "PayeeWallet") - .WithMany() - .HasForeignKey("PayeeWalletId") - .HasConstraintName("fk_payment_transactions_wallets_payee_wallet_id"); - - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "PayerWallet") - .WithMany() - .HasForeignKey("PayerWalletId") - .HasConstraintName("fk_payment_transactions_wallets_payer_wallet_id"); - - b.Navigation("PayeeWallet"); - - b.Navigation("PayerWallet"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Wallet", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_wallets_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.WalletPocket", b => - { - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "Wallet") - .WithMany("Pockets") - .HasForeignKey("WalletId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_wallet_pockets_wallets_wallet_id"); - - b.Navigation("Wallet"); - }); - - modelBuilder.Entity("PostPostCategory", b => - { - b.HasOne("DysonNetwork.Sphere.Post.PostCategory", null) - .WithMany() - .HasForeignKey("CategoriesId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_category_links_post_categories_categories_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany() - .HasForeignKey("PostsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_category_links_posts_posts_id"); - }); - - modelBuilder.Entity("PostPostCollection", b => - { - b.HasOne("DysonNetwork.Sphere.Post.PostCollection", null) - .WithMany() - .HasForeignKey("CollectionsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_collection_links_post_collections_collections_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany() - .HasForeignKey("PostsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_collection_links_posts_posts_id"); - }); - - modelBuilder.Entity("PostPostTag", b => - { - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany() - .HasForeignKey("PostsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_tag_links_posts_posts_id"); - - b.HasOne("DysonNetwork.Sphere.Post.PostTag", null) - .WithMany() - .HasForeignKey("TagsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_tag_links_post_tags_tags_id"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Account", 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"); - - b.Navigation("Subscriptions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatRoom", b => - { - b.Navigation("Members"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.Message", b => - { - b.Navigation("OutdatedAttachments"); - - b.Navigation("Reactions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroup", b => - { - b.Navigation("Members"); - - b.Navigation("Nodes"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.Post", b => - { - b.Navigation("OutdatedAttachments"); - - b.Navigation("Reactions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.Publisher", b => - { - b.Navigation("Collections"); - - b.Navigation("Members"); - - b.Navigation("Posts"); - - b.Navigation("Subscriptions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.Realm", b => - { - b.Navigation("ChatRooms"); - - b.Navigation("Members"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Wallet", b => - { - b.Navigation("Pockets"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/DysonNetwork.Sphere/Migrations/20250615083256_AddAccountConnection.cs b/DysonNetwork.Sphere/Migrations/20250615083256_AddAccountConnection.cs deleted file mode 100644 index 0fa3938..0000000 --- a/DysonNetwork.Sphere/Migrations/20250615083256_AddAccountConnection.cs +++ /dev/null @@ -1,54 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; -using NodaTime; - -#nullable disable - -namespace DysonNetwork.Sphere.Migrations -{ - /// - public partial class AddAccountConnection : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "account_connections", - columns: table => new - { - id = table.Column(type: "uuid", nullable: false), - provider = table.Column(type: "character varying(4096)", maxLength: 4096, nullable: false), - provided_identifier = table.Column(type: "character varying(8192)", maxLength: 8192, nullable: false), - access_token = table.Column(type: "character varying(4096)", maxLength: 4096, nullable: true), - refresh_token = table.Column(type: "character varying(4096)", maxLength: 4096, nullable: true), - last_used_at = table.Column(type: "timestamp with time zone", nullable: true), - account_id = table.Column(type: "uuid", nullable: false), - created_at = table.Column(type: "timestamp with time zone", nullable: false), - updated_at = table.Column(type: "timestamp with time zone", nullable: false), - deleted_at = table.Column(type: "timestamp with time zone", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("pk_account_connections", x => x.id); - table.ForeignKey( - name: "fk_account_connections_accounts_account_id", - column: x => x.account_id, - principalTable: "accounts", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateIndex( - name: "ix_account_connections_account_id", - table: "account_connections", - column: "account_id"); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "account_connections"); - } - } -} diff --git a/DysonNetwork.Sphere/Migrations/20250615154147_EnrichAccountConnection.Designer.cs b/DysonNetwork.Sphere/Migrations/20250615154147_EnrichAccountConnection.Designer.cs deleted file mode 100644 index 478a54b..0000000 --- a/DysonNetwork.Sphere/Migrations/20250615154147_EnrichAccountConnection.Designer.cs +++ /dev/null @@ -1,3626 +0,0 @@ -// -using System; -using System.Collections.Generic; -using System.Text.Json; -using DysonNetwork.Sphere; -using DysonNetwork.Sphere.Account; -using DysonNetwork.Sphere.Chat; -using DysonNetwork.Sphere.Storage; -using DysonNetwork.Sphere.Wallet; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using NetTopologySuite.Geometries; -using NodaTime; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; -using NpgsqlTypes; - -#nullable disable - -namespace DysonNetwork.Sphere.Migrations -{ - [DbContext(typeof(AppDatabase))] - [Migration("20250615154147_EnrichAccountConnection")] - partial class EnrichAccountConnection - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "9.0.3") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "postgis"); - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Account", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("ActivatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("activated_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("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("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.Sphere.Account.AccountAuthFactor", 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.Sphere.Account.AccountConnection", 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.Sphere.Account.AccountContact", 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("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.Sphere.Account.ActionLog", 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("geometry") - .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.HasIndex("SessionId") - .HasDatabaseName("ix_action_logs_session_id"); - - b.ToTable("action_logs", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Badge", 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.Sphere.Account.CheckInResult", 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("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.Sphere.Account.MagicSpell", 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.Sphere.Account.Notification", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("Content") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .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>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("Priority") - .HasColumnType("integer") - .HasColumnName("priority"); - - b.Property("Subtitle") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("subtitle"); - - b.Property("Title") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("title"); - - b.Property("Topic") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("topic"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("ViewedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("viewed_at"); - - b.HasKey("Id") - .HasName("pk_notifications"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_notifications_account_id"); - - b.ToTable("notifications", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.NotificationPushSubscription", 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(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("device_id"); - - b.Property("DeviceToken") - .IsRequired() - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("device_token"); - - b.Property("LastUsedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_used_at"); - - b.Property("Provider") - .HasColumnType("integer") - .HasColumnName("provider"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_notification_push_subscriptions"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_notification_push_subscriptions_account_id"); - - b.HasIndex("DeviceToken", "DeviceId", "AccountId") - .IsUnique() - .HasDatabaseName("ix_notification_push_subscriptions_device_token_device_id_acco"); - - b.ToTable("notification_push_subscriptions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Profile", 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("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_id"); - - 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("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("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - b.Property("Pronouns") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("pronouns"); - - b.Property("StellarMembership") - .HasColumnType("jsonb") - .HasColumnName("stellar_membership"); - - 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("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.Sphere.Account.Relationship", 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.Sphere.Account.Status", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - 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("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("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.Sphere.Auth.Challenge", 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") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("device_id"); - - 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("geometry") - .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("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_challenges"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_auth_challenges_account_id"); - - b.ToTable("auth_challenges", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Session", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("ChallengeId") - .HasColumnType("uuid") - .HasColumnName("challenge_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("Label") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("label"); - - b.Property("LastGrantedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_granted_at"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - 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.ToTable("auth_sessions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatMember", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("BreakUntil") - .HasColumnType("timestamp with time zone") - .HasColumnName("break_until"); - - b.Property("ChatRoomId") - .HasColumnType("uuid") - .HasColumnName("chat_room_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("IsBot") - .HasColumnType("boolean") - .HasColumnName("is_bot"); - - b.Property("JoinedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("joined_at"); - - b.Property("LastReadAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_read_at"); - - b.Property("LeaveAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("leave_at"); - - b.Property("Nick") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("nick"); - - b.Property("Notify") - .HasColumnType("integer") - .HasColumnName("notify"); - - b.Property("Role") - .HasColumnType("integer") - .HasColumnName("role"); - - b.Property("TimeoutCause") - .HasColumnType("jsonb") - .HasColumnName("timeout_cause"); - - b.Property("TimeoutUntil") - .HasColumnType("timestamp with time zone") - .HasColumnName("timeout_until"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_chat_members"); - - b.HasAlternateKey("ChatRoomId", "AccountId") - .HasName("ak_chat_members_chat_room_id_account_id"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_chat_members_account_id"); - - b.ToTable("chat_members", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatRoom", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("Background") - .HasColumnType("jsonb") - .HasColumnName("background"); - - b.Property("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_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("Description") - .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") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("Picture") - .HasColumnType("jsonb") - .HasColumnName("picture"); - - b.Property("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - b.Property("RealmId") - .HasColumnType("uuid") - .HasColumnName("realm_id"); - - b.Property("Type") - .HasColumnType("integer") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_chat_rooms"); - - b.HasIndex("RealmId") - .HasDatabaseName("ix_chat_rooms_realm_id"); - - b.ToTable("chat_rooms", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.Message", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property>("Attachments") - .IsRequired() - .HasColumnType("jsonb") - .HasColumnName("attachments"); - - b.Property("ChatRoomId") - .HasColumnType("uuid") - .HasColumnName("chat_room_id"); - - b.Property("Content") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .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("EditedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("edited_at"); - - b.Property("ForwardedMessageId") - .HasColumnType("uuid") - .HasColumnName("forwarded_message_id"); - - b.Property>("MembersMentioned") - .HasColumnType("jsonb") - .HasColumnName("members_mentioned"); - - b.Property>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("Nonce") - .IsRequired() - .HasMaxLength(36) - .HasColumnType("character varying(36)") - .HasColumnName("nonce"); - - b.Property("RepliedMessageId") - .HasColumnType("uuid") - .HasColumnName("replied_message_id"); - - b.Property("SenderId") - .HasColumnType("uuid") - .HasColumnName("sender_id"); - - 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_chat_messages"); - - b.HasIndex("ChatRoomId") - .HasDatabaseName("ix_chat_messages_chat_room_id"); - - b.HasIndex("ForwardedMessageId") - .HasDatabaseName("ix_chat_messages_forwarded_message_id"); - - b.HasIndex("RepliedMessageId") - .HasDatabaseName("ix_chat_messages_replied_message_id"); - - b.HasIndex("SenderId") - .HasDatabaseName("ix_chat_messages_sender_id"); - - b.ToTable("chat_messages", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.MessageReaction", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("Attitude") - .HasColumnType("integer") - .HasColumnName("attitude"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("MessageId") - .HasColumnType("uuid") - .HasColumnName("message_id"); - - b.Property("SenderId") - .HasColumnType("uuid") - .HasColumnName("sender_id"); - - b.Property("Symbol") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("symbol"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_chat_reactions"); - - b.HasIndex("MessageId") - .HasDatabaseName("ix_chat_reactions_message_id"); - - b.HasIndex("SenderId") - .HasDatabaseName("ix_chat_reactions_sender_id"); - - b.ToTable("chat_reactions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.RealtimeCall", 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("EndedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("ended_at"); - - b.Property("ProviderName") - .HasColumnType("text") - .HasColumnName("provider_name"); - - b.Property("RoomId") - .HasColumnType("uuid") - .HasColumnName("room_id"); - - b.Property("SenderId") - .HasColumnType("uuid") - .HasColumnName("sender_id"); - - b.Property("SessionId") - .HasColumnType("text") - .HasColumnName("session_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("UpstreamConfigJson") - .HasColumnType("jsonb") - .HasColumnName("upstream"); - - b.HasKey("Id") - .HasName("pk_chat_realtime_call"); - - b.HasIndex("RoomId") - .HasDatabaseName("ix_chat_realtime_call_room_id"); - - b.HasIndex("SenderId") - .HasDatabaseName("ix_chat_realtime_call_sender_id"); - - b.ToTable("chat_realtime_call", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomApp", 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("Name") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("slug"); - - b.Property("Status") - .HasColumnType("integer") - .HasColumnName("status"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("VerifiedAs") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("verified_as"); - - b.Property("VerifiedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("verified_at"); - - b.HasKey("Id") - .HasName("pk_custom_apps"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_custom_apps_publisher_id"); - - b.ToTable("custom_apps", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomAppSecret", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AppId") - .HasColumnType("uuid") - .HasColumnName("app_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("Remarks") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("remarks"); - - b.Property("Secret") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("secret"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_custom_app_secrets"); - - b.HasIndex("AppId") - .HasDatabaseName("ix_custom_app_secrets_app_id"); - - b.ToTable("custom_app_secrets", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroup", 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.Sphere.Permission.PermissionGroupMember", 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.Sphere.Permission.PermissionNode", 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("Area") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("area"); - - 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("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", "Area", "Actor") - .HasDatabaseName("ix_permission_nodes_key_area_actor"); - - b.ToTable("permission_nodes", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.Post", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property>("Attachments") - .IsRequired() - .HasColumnType("jsonb") - .HasColumnName("attachments"); - - b.Property("Content") - .HasColumnType("text") - .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("Description") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("Downvotes") - .HasColumnType("integer") - .HasColumnName("downvotes"); - - b.Property("EditedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("edited_at"); - - b.Property("ForwardedPostId") - .HasColumnType("uuid") - .HasColumnName("forwarded_post_id"); - - b.Property("Language") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("language"); - - b.Property>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("PublishedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("published_at"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("RepliedPostId") - .HasColumnType("uuid") - .HasColumnName("replied_post_id"); - - b.Property("SearchVector") - .IsRequired() - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("tsvector") - .HasColumnName("search_vector") - .HasAnnotation("Npgsql:TsVectorConfig", "simple") - .HasAnnotation("Npgsql:TsVectorProperties", new[] { "Title", "Description", "Content" }); - - b.Property("Title") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("title"); - - b.Property("Type") - .HasColumnType("integer") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("Upvotes") - .HasColumnType("integer") - .HasColumnName("upvotes"); - - b.Property("ViewsTotal") - .HasColumnType("integer") - .HasColumnName("views_total"); - - b.Property("ViewsUnique") - .HasColumnType("integer") - .HasColumnName("views_unique"); - - b.Property("Visibility") - .HasColumnType("integer") - .HasColumnName("visibility"); - - b.HasKey("Id") - .HasName("pk_posts"); - - b.HasIndex("ForwardedPostId") - .HasDatabaseName("ix_posts_forwarded_post_id"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_posts_publisher_id"); - - b.HasIndex("RepliedPostId") - .HasDatabaseName("ix_posts_replied_post_id"); - - b.HasIndex("SearchVector") - .HasDatabaseName("ix_posts_search_vector"); - - NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex("SearchVector"), "GIN"); - - b.ToTable("posts", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostCategory", 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("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("name"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_categories"); - - b.ToTable("post_categories", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostCollection", 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("Description") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("name"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_collections"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_post_collections_publisher_id"); - - b.ToTable("post_collections", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostReaction", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("Attitude") - .HasColumnType("integer") - .HasColumnName("attitude"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("PostId") - .HasColumnType("uuid") - .HasColumnName("post_id"); - - b.Property("Symbol") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("symbol"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_reactions"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_post_reactions_account_id"); - - b.HasIndex("PostId") - .HasDatabaseName("ix_post_reactions_post_id"); - - b.ToTable("post_reactions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostTag", 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("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("name"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_tags"); - - b.ToTable("post_tags", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.Publisher", 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("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_id"); - - b.Property("Bio") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("bio"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - 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("Picture") - .HasColumnType("jsonb") - .HasColumnName("picture"); - - b.Property("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - b.Property("RealmId") - .HasColumnType("uuid") - .HasColumnName("realm_id"); - - b.Property("Type") - .HasColumnType("integer") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("Verification") - .HasColumnType("jsonb") - .HasColumnName("verification"); - - b.HasKey("Id") - .HasName("pk_publishers"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_publishers_account_id"); - - b.HasIndex("Name") - .IsUnique() - .HasDatabaseName("ix_publishers_name"); - - b.HasIndex("RealmId") - .HasDatabaseName("ix_publishers_realm_id"); - - b.ToTable("publishers", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherFeature", 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("ExpiredAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expired_at"); - - b.Property("Flag") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("flag"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_publisher_features"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_publisher_features_publisher_id"); - - b.ToTable("publisher_features", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherMember", b => - { - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_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("Role") - .HasColumnType("integer") - .HasColumnName("role"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("PublisherId", "AccountId") - .HasName("pk_publisher_members"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_publisher_members_account_id"); - - b.ToTable("publisher_members", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherSubscription", 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("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("Status") - .HasColumnType("integer") - .HasColumnName("status"); - - b.Property("Tier") - .HasColumnType("integer") - .HasColumnName("tier"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_publisher_subscriptions"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_publisher_subscriptions_account_id"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_publisher_subscriptions_publisher_id"); - - b.ToTable("publisher_subscriptions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.Realm", 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("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_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("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("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - 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("AccountId") - .HasDatabaseName("ix_realms_account_id"); - - b.HasIndex("Slug") - .IsUnique() - .HasDatabaseName("ix_realms_slug"); - - b.ToTable("realms", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.RealmMember", 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.HasIndex("AccountId") - .HasDatabaseName("ix_realm_members_account_id"); - - b.ToTable("realm_members", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.Sticker", 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("Image") - .HasColumnType("jsonb") - .HasColumnName("image"); - - b.Property("ImageId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("image_id"); - - b.Property("PackId") - .HasColumnType("uuid") - .HasColumnName("pack_id"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_stickers"); - - b.HasIndex("PackId") - .HasDatabaseName("ix_stickers_pack_id"); - - b.HasIndex("Slug") - .HasDatabaseName("ix_stickers_slug"); - - b.ToTable("stickers", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.StickerPack", 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("Description") - .IsRequired() - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("Prefix") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("prefix"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_sticker_packs"); - - b.HasIndex("Prefix") - .IsUnique() - .HasDatabaseName("ix_sticker_packs_prefix"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_sticker_packs_publisher_id"); - - b.ToTable("sticker_packs", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFile", b => - { - b.Property("Id") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .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("Description") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property>("FileMeta") - .HasColumnType("jsonb") - .HasColumnName("file_meta"); - - b.Property("HasCompression") - .HasColumnType("boolean") - .HasColumnName("has_compression"); - - b.Property("Hash") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("hash"); - - b.Property("IsMarkedRecycle") - .HasColumnType("boolean") - .HasColumnName("is_marked_recycle"); - - b.Property("MessageId") - .HasColumnType("uuid") - .HasColumnName("message_id"); - - b.Property("MimeType") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("mime_type"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("PostId") - .HasColumnType("uuid") - .HasColumnName("post_id"); - - b.Property>("SensitiveMarks") - .HasColumnType("jsonb") - .HasColumnName("sensitive_marks"); - - b.Property("Size") - .HasColumnType("bigint") - .HasColumnName("size"); - - b.Property("StorageId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("storage_id"); - - b.Property("StorageUrl") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("storage_url"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("UploadedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("uploaded_at"); - - b.Property("UploadedTo") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("uploaded_to"); - - b.Property>("UserMeta") - .HasColumnType("jsonb") - .HasColumnName("user_meta"); - - b.HasKey("Id") - .HasName("pk_files"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_files_account_id"); - - b.HasIndex("MessageId") - .HasDatabaseName("ix_files_message_id"); - - b.HasIndex("PostId") - .HasDatabaseName("ix_files_post_id"); - - b.ToTable("files", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFileReference", 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("ExpiredAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expired_at"); - - b.Property("FileId") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("file_id"); - - b.Property("ResourceId") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("resource_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("Usage") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("usage"); - - b.HasKey("Id") - .HasName("pk_file_references"); - - b.HasIndex("FileId") - .HasDatabaseName("ix_file_references_file_id"); - - b.ToTable("file_references", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Coupon", 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.Sphere.Wallet.Order", 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("ExpiredAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expired_at"); - - b.Property("IssuerAppId") - .HasColumnType("uuid") - .HasColumnName("issuer_app_id"); - - b.Property("PayeeWalletId") - .HasColumnType("uuid") - .HasColumnName("payee_wallet_id"); - - 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("IssuerAppId") - .HasDatabaseName("ix_payment_orders_issuer_app_id"); - - 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.Sphere.Wallet.Subscription", 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.ToTable("wallet_subscriptions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Transaction", 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.Sphere.Wallet.Wallet", 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.Sphere.Wallet.WalletPocket", 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("PostPostCategory", b => - { - b.Property("CategoriesId") - .HasColumnType("uuid") - .HasColumnName("categories_id"); - - b.Property("PostsId") - .HasColumnType("uuid") - .HasColumnName("posts_id"); - - b.HasKey("CategoriesId", "PostsId") - .HasName("pk_post_category_links"); - - b.HasIndex("PostsId") - .HasDatabaseName("ix_post_category_links_posts_id"); - - b.ToTable("post_category_links", (string)null); - }); - - modelBuilder.Entity("PostPostCollection", b => - { - b.Property("CollectionsId") - .HasColumnType("uuid") - .HasColumnName("collections_id"); - - b.Property("PostsId") - .HasColumnType("uuid") - .HasColumnName("posts_id"); - - b.HasKey("CollectionsId", "PostsId") - .HasName("pk_post_collection_links"); - - b.HasIndex("PostsId") - .HasDatabaseName("ix_post_collection_links_posts_id"); - - b.ToTable("post_collection_links", (string)null); - }); - - modelBuilder.Entity("PostPostTag", b => - { - b.Property("PostsId") - .HasColumnType("uuid") - .HasColumnName("posts_id"); - - b.Property("TagsId") - .HasColumnType("uuid") - .HasColumnName("tags_id"); - - b.HasKey("PostsId", "TagsId") - .HasName("pk_post_tag_links"); - - b.HasIndex("TagsId") - .HasDatabaseName("ix_post_tag_links_tags_id"); - - b.ToTable("post_tag_links", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.AccountAuthFactor", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("AuthFactors") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_auth_factors_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.AccountConnection", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Connections") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_connections_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.AccountContact", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Contacts") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_contacts_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.ActionLog", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_action_logs_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Auth.Session", "Session") - .WithMany() - .HasForeignKey("SessionId") - .HasConstraintName("fk_action_logs_auth_sessions_session_id"); - - b.Navigation("Account"); - - b.Navigation("Session"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Badge", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Badges") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_badges_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.CheckInResult", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_check_in_results_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.MagicSpell", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .HasConstraintName("fk_magic_spells_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Notification", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_notifications_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.NotificationPushSubscription", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_notification_push_subscriptions_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Profile", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithOne("Profile") - .HasForeignKey("DysonNetwork.Sphere.Account.Profile", "AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_profiles_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Relationship", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("OutgoingRelationships") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_relationships_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Account.Account", "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.Sphere.Account.Status", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_statuses_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Challenge", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Challenges") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_challenges_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Session", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Sessions") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_sessions_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Auth.Challenge", "Challenge") - .WithMany() - .HasForeignKey("ChallengeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_sessions_auth_challenges_challenge_id"); - - b.Navigation("Account"); - - b.Navigation("Challenge"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatMember", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_members_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatRoom", "ChatRoom") - .WithMany("Members") - .HasForeignKey("ChatRoomId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_members_chat_rooms_chat_room_id"); - - b.Navigation("Account"); - - b.Navigation("ChatRoom"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatRoom", b => - { - b.HasOne("DysonNetwork.Sphere.Realm.Realm", "Realm") - .WithMany("ChatRooms") - .HasForeignKey("RealmId") - .HasConstraintName("fk_chat_rooms_realms_realm_id"); - - b.Navigation("Realm"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.Message", b => - { - b.HasOne("DysonNetwork.Sphere.Chat.ChatRoom", "ChatRoom") - .WithMany() - .HasForeignKey("ChatRoomId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_messages_chat_rooms_chat_room_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.Message", "ForwardedMessage") - .WithMany() - .HasForeignKey("ForwardedMessageId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_chat_messages_chat_messages_forwarded_message_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.Message", "RepliedMessage") - .WithMany() - .HasForeignKey("RepliedMessageId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_chat_messages_chat_messages_replied_message_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatMember", "Sender") - .WithMany() - .HasForeignKey("SenderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_messages_chat_members_sender_id"); - - b.Navigation("ChatRoom"); - - b.Navigation("ForwardedMessage"); - - b.Navigation("RepliedMessage"); - - b.Navigation("Sender"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.MessageReaction", b => - { - b.HasOne("DysonNetwork.Sphere.Chat.Message", "Message") - .WithMany("Reactions") - .HasForeignKey("MessageId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_reactions_chat_messages_message_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatMember", "Sender") - .WithMany() - .HasForeignKey("SenderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_reactions_chat_members_sender_id"); - - b.Navigation("Message"); - - b.Navigation("Sender"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.RealtimeCall", b => - { - b.HasOne("DysonNetwork.Sphere.Chat.ChatRoom", "Room") - .WithMany() - .HasForeignKey("RoomId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_realtime_call_chat_rooms_room_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatMember", "Sender") - .WithMany() - .HasForeignKey("SenderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_realtime_call_chat_members_sender_id"); - - b.Navigation("Room"); - - b.Navigation("Sender"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomApp", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Developer") - .WithMany() - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_custom_apps_publishers_publisher_id"); - - b.Navigation("Developer"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomAppSecret", b => - { - b.HasOne("DysonNetwork.Sphere.Developer.CustomApp", "App") - .WithMany() - .HasForeignKey("AppId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_custom_app_secrets_custom_apps_app_id"); - - b.Navigation("App"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroupMember", b => - { - b.HasOne("DysonNetwork.Sphere.Permission.PermissionGroup", "Group") - .WithMany("Members") - .HasForeignKey("GroupId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_permission_group_members_permission_groups_group_id"); - - b.Navigation("Group"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionNode", b => - { - b.HasOne("DysonNetwork.Sphere.Permission.PermissionGroup", "Group") - .WithMany("Nodes") - .HasForeignKey("GroupId") - .HasConstraintName("fk_permission_nodes_permission_groups_group_id"); - - b.Navigation("Group"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.Post", b => - { - b.HasOne("DysonNetwork.Sphere.Post.Post", "ForwardedPost") - .WithMany() - .HasForeignKey("ForwardedPostId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_posts_posts_forwarded_post_id"); - - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Posts") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_posts_publishers_publisher_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", "RepliedPost") - .WithMany() - .HasForeignKey("RepliedPostId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_posts_posts_replied_post_id"); - - b.Navigation("ForwardedPost"); - - b.Navigation("Publisher"); - - b.Navigation("RepliedPost"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostCollection", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Collections") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_collections_publishers_publisher_id"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostReaction", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_reactions_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", "Post") - .WithMany("Reactions") - .HasForeignKey("PostId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_reactions_posts_post_id"); - - b.Navigation("Account"); - - b.Navigation("Post"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.Publisher", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .HasConstraintName("fk_publishers_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Realm.Realm", "Realm") - .WithMany() - .HasForeignKey("RealmId") - .HasConstraintName("fk_publishers_realms_realm_id"); - - b.Navigation("Account"); - - b.Navigation("Realm"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherFeature", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany() - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_features_publishers_publisher_id"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherMember", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_members_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Members") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_members_publishers_publisher_id"); - - b.Navigation("Account"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherSubscription", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_subscriptions_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Subscriptions") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_subscriptions_publishers_publisher_id"); - - b.Navigation("Account"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.Realm", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_realms_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.RealmMember", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_realm_members_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Realm.Realm", "Realm") - .WithMany("Members") - .HasForeignKey("RealmId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_realm_members_realms_realm_id"); - - b.Navigation("Account"); - - b.Navigation("Realm"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.Sticker", b => - { - b.HasOne("DysonNetwork.Sphere.Sticker.StickerPack", "Pack") - .WithMany() - .HasForeignKey("PackId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_stickers_sticker_packs_pack_id"); - - b.Navigation("Pack"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.StickerPack", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany() - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_sticker_packs_publishers_publisher_id"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFile", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_files_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.Message", null) - .WithMany("OutdatedAttachments") - .HasForeignKey("MessageId") - .HasConstraintName("fk_files_chat_messages_message_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany("OutdatedAttachments") - .HasForeignKey("PostId") - .HasConstraintName("fk_files_posts_post_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFileReference", b => - { - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "File") - .WithMany() - .HasForeignKey("FileId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_file_references_files_file_id"); - - b.Navigation("File"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Order", b => - { - b.HasOne("DysonNetwork.Sphere.Developer.CustomApp", "IssuerApp") - .WithMany() - .HasForeignKey("IssuerAppId") - .HasConstraintName("fk_payment_orders_custom_apps_issuer_app_id"); - - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "PayeeWallet") - .WithMany() - .HasForeignKey("PayeeWalletId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_payment_orders_wallets_payee_wallet_id"); - - b.HasOne("DysonNetwork.Sphere.Wallet.Transaction", "Transaction") - .WithMany() - .HasForeignKey("TransactionId") - .HasConstraintName("fk_payment_orders_payment_transactions_transaction_id"); - - b.Navigation("IssuerApp"); - - b.Navigation("PayeeWallet"); - - b.Navigation("Transaction"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Subscription", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Subscriptions") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_wallet_subscriptions_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Wallet.Coupon", "Coupon") - .WithMany() - .HasForeignKey("CouponId") - .HasConstraintName("fk_wallet_subscriptions_wallet_coupons_coupon_id"); - - b.Navigation("Account"); - - b.Navigation("Coupon"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Transaction", b => - { - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "PayeeWallet") - .WithMany() - .HasForeignKey("PayeeWalletId") - .HasConstraintName("fk_payment_transactions_wallets_payee_wallet_id"); - - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "PayerWallet") - .WithMany() - .HasForeignKey("PayerWalletId") - .HasConstraintName("fk_payment_transactions_wallets_payer_wallet_id"); - - b.Navigation("PayeeWallet"); - - b.Navigation("PayerWallet"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Wallet", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_wallets_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.WalletPocket", b => - { - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "Wallet") - .WithMany("Pockets") - .HasForeignKey("WalletId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_wallet_pockets_wallets_wallet_id"); - - b.Navigation("Wallet"); - }); - - modelBuilder.Entity("PostPostCategory", b => - { - b.HasOne("DysonNetwork.Sphere.Post.PostCategory", null) - .WithMany() - .HasForeignKey("CategoriesId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_category_links_post_categories_categories_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany() - .HasForeignKey("PostsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_category_links_posts_posts_id"); - }); - - modelBuilder.Entity("PostPostCollection", b => - { - b.HasOne("DysonNetwork.Sphere.Post.PostCollection", null) - .WithMany() - .HasForeignKey("CollectionsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_collection_links_post_collections_collections_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany() - .HasForeignKey("PostsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_collection_links_posts_posts_id"); - }); - - modelBuilder.Entity("PostPostTag", b => - { - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany() - .HasForeignKey("PostsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_tag_links_posts_posts_id"); - - b.HasOne("DysonNetwork.Sphere.Post.PostTag", null) - .WithMany() - .HasForeignKey("TagsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_tag_links_post_tags_tags_id"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Account", 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"); - - b.Navigation("Subscriptions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatRoom", b => - { - b.Navigation("Members"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.Message", b => - { - b.Navigation("OutdatedAttachments"); - - b.Navigation("Reactions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroup", b => - { - b.Navigation("Members"); - - b.Navigation("Nodes"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.Post", b => - { - b.Navigation("OutdatedAttachments"); - - b.Navigation("Reactions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.Publisher", b => - { - b.Navigation("Collections"); - - b.Navigation("Members"); - - b.Navigation("Posts"); - - b.Navigation("Subscriptions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.Realm", b => - { - b.Navigation("ChatRooms"); - - b.Navigation("Members"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Wallet", b => - { - b.Navigation("Pockets"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/DysonNetwork.Sphere/Migrations/20250615154147_EnrichAccountConnection.cs b/DysonNetwork.Sphere/Migrations/20250615154147_EnrichAccountConnection.cs deleted file mode 100644 index 15a9722..0000000 --- a/DysonNetwork.Sphere/Migrations/20250615154147_EnrichAccountConnection.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System.Collections.Generic; -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace DysonNetwork.Sphere.Migrations -{ - /// - public partial class EnrichAccountConnection : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn>( - name: "meta", - table: "account_connections", - type: "jsonb", - nullable: true); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "meta", - table: "account_connections"); - } - } -} diff --git a/DysonNetwork.Sphere/Migrations/20250621191505_WalletOrderAppDX.Designer.cs b/DysonNetwork.Sphere/Migrations/20250621191505_WalletOrderAppDX.Designer.cs deleted file mode 100644 index 3664182..0000000 --- a/DysonNetwork.Sphere/Migrations/20250621191505_WalletOrderAppDX.Designer.cs +++ /dev/null @@ -1,3633 +0,0 @@ -// -using System; -using System.Collections.Generic; -using System.Text.Json; -using DysonNetwork.Sphere; -using DysonNetwork.Sphere.Account; -using DysonNetwork.Sphere.Chat; -using DysonNetwork.Sphere.Storage; -using DysonNetwork.Sphere.Wallet; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using NetTopologySuite.Geometries; -using NodaTime; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; -using NpgsqlTypes; - -#nullable disable - -namespace DysonNetwork.Sphere.Migrations -{ - [DbContext(typeof(AppDatabase))] - [Migration("20250621191505_WalletOrderAppDX")] - partial class WalletOrderAppDX - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "9.0.3") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "postgis"); - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Account", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("ActivatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("activated_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("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("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.Sphere.Account.AccountAuthFactor", 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.Sphere.Account.AccountConnection", 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.Sphere.Account.AccountContact", 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("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.Sphere.Account.ActionLog", 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("geometry") - .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.HasIndex("SessionId") - .HasDatabaseName("ix_action_logs_session_id"); - - b.ToTable("action_logs", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Badge", 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.Sphere.Account.CheckInResult", 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("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.Sphere.Account.MagicSpell", 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.Sphere.Account.Notification", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("Content") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .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>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("Priority") - .HasColumnType("integer") - .HasColumnName("priority"); - - b.Property("Subtitle") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("subtitle"); - - b.Property("Title") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("title"); - - b.Property("Topic") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("topic"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("ViewedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("viewed_at"); - - b.HasKey("Id") - .HasName("pk_notifications"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_notifications_account_id"); - - b.ToTable("notifications", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.NotificationPushSubscription", 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(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("device_id"); - - b.Property("DeviceToken") - .IsRequired() - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("device_token"); - - b.Property("LastUsedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_used_at"); - - b.Property("Provider") - .HasColumnType("integer") - .HasColumnName("provider"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_notification_push_subscriptions"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_notification_push_subscriptions_account_id"); - - b.HasIndex("DeviceToken", "DeviceId", "AccountId") - .IsUnique() - .HasDatabaseName("ix_notification_push_subscriptions_device_token_device_id_acco"); - - b.ToTable("notification_push_subscriptions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Profile", 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("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_id"); - - 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("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("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - b.Property("Pronouns") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("pronouns"); - - b.Property("StellarMembership") - .HasColumnType("jsonb") - .HasColumnName("stellar_membership"); - - 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("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.Sphere.Account.Relationship", 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.Sphere.Account.Status", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - 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("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("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.Sphere.Auth.Challenge", 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") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("device_id"); - - 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("geometry") - .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("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_challenges"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_auth_challenges_account_id"); - - b.ToTable("auth_challenges", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Session", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("ChallengeId") - .HasColumnType("uuid") - .HasColumnName("challenge_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("Label") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("label"); - - b.Property("LastGrantedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_granted_at"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - 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.ToTable("auth_sessions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatMember", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("BreakUntil") - .HasColumnType("timestamp with time zone") - .HasColumnName("break_until"); - - b.Property("ChatRoomId") - .HasColumnType("uuid") - .HasColumnName("chat_room_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("IsBot") - .HasColumnType("boolean") - .HasColumnName("is_bot"); - - b.Property("JoinedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("joined_at"); - - b.Property("LastReadAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_read_at"); - - b.Property("LeaveAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("leave_at"); - - b.Property("Nick") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("nick"); - - b.Property("Notify") - .HasColumnType("integer") - .HasColumnName("notify"); - - b.Property("Role") - .HasColumnType("integer") - .HasColumnName("role"); - - b.Property("TimeoutCause") - .HasColumnType("jsonb") - .HasColumnName("timeout_cause"); - - b.Property("TimeoutUntil") - .HasColumnType("timestamp with time zone") - .HasColumnName("timeout_until"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_chat_members"); - - b.HasAlternateKey("ChatRoomId", "AccountId") - .HasName("ak_chat_members_chat_room_id_account_id"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_chat_members_account_id"); - - b.ToTable("chat_members", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatRoom", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("Background") - .HasColumnType("jsonb") - .HasColumnName("background"); - - b.Property("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_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("Description") - .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") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("Picture") - .HasColumnType("jsonb") - .HasColumnName("picture"); - - b.Property("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - b.Property("RealmId") - .HasColumnType("uuid") - .HasColumnName("realm_id"); - - b.Property("Type") - .HasColumnType("integer") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_chat_rooms"); - - b.HasIndex("RealmId") - .HasDatabaseName("ix_chat_rooms_realm_id"); - - b.ToTable("chat_rooms", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.Message", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property>("Attachments") - .IsRequired() - .HasColumnType("jsonb") - .HasColumnName("attachments"); - - b.Property("ChatRoomId") - .HasColumnType("uuid") - .HasColumnName("chat_room_id"); - - b.Property("Content") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .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("EditedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("edited_at"); - - b.Property("ForwardedMessageId") - .HasColumnType("uuid") - .HasColumnName("forwarded_message_id"); - - b.Property>("MembersMentioned") - .HasColumnType("jsonb") - .HasColumnName("members_mentioned"); - - b.Property>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("Nonce") - .IsRequired() - .HasMaxLength(36) - .HasColumnType("character varying(36)") - .HasColumnName("nonce"); - - b.Property("RepliedMessageId") - .HasColumnType("uuid") - .HasColumnName("replied_message_id"); - - b.Property("SenderId") - .HasColumnType("uuid") - .HasColumnName("sender_id"); - - 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_chat_messages"); - - b.HasIndex("ChatRoomId") - .HasDatabaseName("ix_chat_messages_chat_room_id"); - - b.HasIndex("ForwardedMessageId") - .HasDatabaseName("ix_chat_messages_forwarded_message_id"); - - b.HasIndex("RepliedMessageId") - .HasDatabaseName("ix_chat_messages_replied_message_id"); - - b.HasIndex("SenderId") - .HasDatabaseName("ix_chat_messages_sender_id"); - - b.ToTable("chat_messages", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.MessageReaction", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("Attitude") - .HasColumnType("integer") - .HasColumnName("attitude"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("MessageId") - .HasColumnType("uuid") - .HasColumnName("message_id"); - - b.Property("SenderId") - .HasColumnType("uuid") - .HasColumnName("sender_id"); - - b.Property("Symbol") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("symbol"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_chat_reactions"); - - b.HasIndex("MessageId") - .HasDatabaseName("ix_chat_reactions_message_id"); - - b.HasIndex("SenderId") - .HasDatabaseName("ix_chat_reactions_sender_id"); - - b.ToTable("chat_reactions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.RealtimeCall", 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("EndedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("ended_at"); - - b.Property("ProviderName") - .HasColumnType("text") - .HasColumnName("provider_name"); - - b.Property("RoomId") - .HasColumnType("uuid") - .HasColumnName("room_id"); - - b.Property("SenderId") - .HasColumnType("uuid") - .HasColumnName("sender_id"); - - b.Property("SessionId") - .HasColumnType("text") - .HasColumnName("session_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("UpstreamConfigJson") - .HasColumnType("jsonb") - .HasColumnName("upstream"); - - b.HasKey("Id") - .HasName("pk_chat_realtime_call"); - - b.HasIndex("RoomId") - .HasDatabaseName("ix_chat_realtime_call_room_id"); - - b.HasIndex("SenderId") - .HasDatabaseName("ix_chat_realtime_call_sender_id"); - - b.ToTable("chat_realtime_call", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomApp", 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("Name") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("slug"); - - b.Property("Status") - .HasColumnType("integer") - .HasColumnName("status"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("VerifiedAs") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("verified_as"); - - b.Property("VerifiedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("verified_at"); - - b.HasKey("Id") - .HasName("pk_custom_apps"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_custom_apps_publisher_id"); - - b.ToTable("custom_apps", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomAppSecret", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AppId") - .HasColumnType("uuid") - .HasColumnName("app_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("Remarks") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("remarks"); - - b.Property("Secret") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("secret"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_custom_app_secrets"); - - b.HasIndex("AppId") - .HasDatabaseName("ix_custom_app_secrets_app_id"); - - b.ToTable("custom_app_secrets", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroup", 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.Sphere.Permission.PermissionGroupMember", 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.Sphere.Permission.PermissionNode", 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("Area") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("area"); - - 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("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", "Area", "Actor") - .HasDatabaseName("ix_permission_nodes_key_area_actor"); - - b.ToTable("permission_nodes", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.Post", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property>("Attachments") - .IsRequired() - .HasColumnType("jsonb") - .HasColumnName("attachments"); - - b.Property("Content") - .HasColumnType("text") - .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("Description") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("Downvotes") - .HasColumnType("integer") - .HasColumnName("downvotes"); - - b.Property("EditedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("edited_at"); - - b.Property("ForwardedPostId") - .HasColumnType("uuid") - .HasColumnName("forwarded_post_id"); - - b.Property("Language") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("language"); - - b.Property>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("PublishedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("published_at"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("RepliedPostId") - .HasColumnType("uuid") - .HasColumnName("replied_post_id"); - - b.Property("SearchVector") - .IsRequired() - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("tsvector") - .HasColumnName("search_vector") - .HasAnnotation("Npgsql:TsVectorConfig", "simple") - .HasAnnotation("Npgsql:TsVectorProperties", new[] { "Title", "Description", "Content" }); - - b.Property("Title") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("title"); - - b.Property("Type") - .HasColumnType("integer") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("Upvotes") - .HasColumnType("integer") - .HasColumnName("upvotes"); - - b.Property("ViewsTotal") - .HasColumnType("integer") - .HasColumnName("views_total"); - - b.Property("ViewsUnique") - .HasColumnType("integer") - .HasColumnName("views_unique"); - - b.Property("Visibility") - .HasColumnType("integer") - .HasColumnName("visibility"); - - b.HasKey("Id") - .HasName("pk_posts"); - - b.HasIndex("ForwardedPostId") - .HasDatabaseName("ix_posts_forwarded_post_id"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_posts_publisher_id"); - - b.HasIndex("RepliedPostId") - .HasDatabaseName("ix_posts_replied_post_id"); - - b.HasIndex("SearchVector") - .HasDatabaseName("ix_posts_search_vector"); - - NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex("SearchVector"), "GIN"); - - b.ToTable("posts", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostCategory", 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("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("name"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_categories"); - - b.ToTable("post_categories", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostCollection", 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("Description") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("name"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_collections"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_post_collections_publisher_id"); - - b.ToTable("post_collections", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostReaction", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("Attitude") - .HasColumnType("integer") - .HasColumnName("attitude"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("PostId") - .HasColumnType("uuid") - .HasColumnName("post_id"); - - b.Property("Symbol") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("symbol"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_reactions"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_post_reactions_account_id"); - - b.HasIndex("PostId") - .HasDatabaseName("ix_post_reactions_post_id"); - - b.ToTable("post_reactions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostTag", 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("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("name"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_tags"); - - b.ToTable("post_tags", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.Publisher", 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("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_id"); - - b.Property("Bio") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("bio"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - 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("Picture") - .HasColumnType("jsonb") - .HasColumnName("picture"); - - b.Property("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - b.Property("RealmId") - .HasColumnType("uuid") - .HasColumnName("realm_id"); - - b.Property("Type") - .HasColumnType("integer") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("Verification") - .HasColumnType("jsonb") - .HasColumnName("verification"); - - b.HasKey("Id") - .HasName("pk_publishers"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_publishers_account_id"); - - b.HasIndex("Name") - .IsUnique() - .HasDatabaseName("ix_publishers_name"); - - b.HasIndex("RealmId") - .HasDatabaseName("ix_publishers_realm_id"); - - b.ToTable("publishers", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherFeature", 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("ExpiredAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expired_at"); - - b.Property("Flag") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("flag"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_publisher_features"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_publisher_features_publisher_id"); - - b.ToTable("publisher_features", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherMember", b => - { - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_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("Role") - .HasColumnType("integer") - .HasColumnName("role"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("PublisherId", "AccountId") - .HasName("pk_publisher_members"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_publisher_members_account_id"); - - b.ToTable("publisher_members", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherSubscription", 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("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("Status") - .HasColumnType("integer") - .HasColumnName("status"); - - b.Property("Tier") - .HasColumnType("integer") - .HasColumnName("tier"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_publisher_subscriptions"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_publisher_subscriptions_account_id"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_publisher_subscriptions_publisher_id"); - - b.ToTable("publisher_subscriptions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.Realm", 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("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_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("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("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - 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("AccountId") - .HasDatabaseName("ix_realms_account_id"); - - b.HasIndex("Slug") - .IsUnique() - .HasDatabaseName("ix_realms_slug"); - - b.ToTable("realms", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.RealmMember", 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.HasIndex("AccountId") - .HasDatabaseName("ix_realm_members_account_id"); - - b.ToTable("realm_members", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.Sticker", 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("Image") - .HasColumnType("jsonb") - .HasColumnName("image"); - - b.Property("ImageId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("image_id"); - - b.Property("PackId") - .HasColumnType("uuid") - .HasColumnName("pack_id"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_stickers"); - - b.HasIndex("PackId") - .HasDatabaseName("ix_stickers_pack_id"); - - b.HasIndex("Slug") - .HasDatabaseName("ix_stickers_slug"); - - b.ToTable("stickers", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.StickerPack", 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("Description") - .IsRequired() - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("Prefix") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("prefix"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_sticker_packs"); - - b.HasIndex("Prefix") - .IsUnique() - .HasDatabaseName("ix_sticker_packs_prefix"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_sticker_packs_publisher_id"); - - b.ToTable("sticker_packs", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFile", b => - { - b.Property("Id") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .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("Description") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property>("FileMeta") - .HasColumnType("jsonb") - .HasColumnName("file_meta"); - - b.Property("HasCompression") - .HasColumnType("boolean") - .HasColumnName("has_compression"); - - b.Property("Hash") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("hash"); - - b.Property("IsMarkedRecycle") - .HasColumnType("boolean") - .HasColumnName("is_marked_recycle"); - - b.Property("MessageId") - .HasColumnType("uuid") - .HasColumnName("message_id"); - - b.Property("MimeType") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("mime_type"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("PostId") - .HasColumnType("uuid") - .HasColumnName("post_id"); - - b.Property>("SensitiveMarks") - .HasColumnType("jsonb") - .HasColumnName("sensitive_marks"); - - b.Property("Size") - .HasColumnType("bigint") - .HasColumnName("size"); - - b.Property("StorageId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("storage_id"); - - b.Property("StorageUrl") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("storage_url"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("UploadedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("uploaded_at"); - - b.Property("UploadedTo") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("uploaded_to"); - - b.Property>("UserMeta") - .HasColumnType("jsonb") - .HasColumnName("user_meta"); - - b.HasKey("Id") - .HasName("pk_files"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_files_account_id"); - - b.HasIndex("MessageId") - .HasDatabaseName("ix_files_message_id"); - - b.HasIndex("PostId") - .HasDatabaseName("ix_files_post_id"); - - b.ToTable("files", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFileReference", 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("ExpiredAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expired_at"); - - b.Property("FileId") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("file_id"); - - b.Property("ResourceId") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("resource_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("Usage") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("usage"); - - b.HasKey("Id") - .HasName("pk_file_references"); - - b.HasIndex("FileId") - .HasDatabaseName("ix_file_references_file_id"); - - b.ToTable("file_references", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Coupon", 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.Sphere.Wallet.Order", 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("IssuerAppId") - .HasColumnType("uuid") - .HasColumnName("issuer_app_id"); - - b.Property>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("PayeeWalletId") - .HasColumnType("uuid") - .HasColumnName("payee_wallet_id"); - - 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("IssuerAppId") - .HasDatabaseName("ix_payment_orders_issuer_app_id"); - - 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.Sphere.Wallet.Subscription", 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.ToTable("wallet_subscriptions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Transaction", 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.Sphere.Wallet.Wallet", 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.Sphere.Wallet.WalletPocket", 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("PostPostCategory", b => - { - b.Property("CategoriesId") - .HasColumnType("uuid") - .HasColumnName("categories_id"); - - b.Property("PostsId") - .HasColumnType("uuid") - .HasColumnName("posts_id"); - - b.HasKey("CategoriesId", "PostsId") - .HasName("pk_post_category_links"); - - b.HasIndex("PostsId") - .HasDatabaseName("ix_post_category_links_posts_id"); - - b.ToTable("post_category_links", (string)null); - }); - - modelBuilder.Entity("PostPostCollection", b => - { - b.Property("CollectionsId") - .HasColumnType("uuid") - .HasColumnName("collections_id"); - - b.Property("PostsId") - .HasColumnType("uuid") - .HasColumnName("posts_id"); - - b.HasKey("CollectionsId", "PostsId") - .HasName("pk_post_collection_links"); - - b.HasIndex("PostsId") - .HasDatabaseName("ix_post_collection_links_posts_id"); - - b.ToTable("post_collection_links", (string)null); - }); - - modelBuilder.Entity("PostPostTag", b => - { - b.Property("PostsId") - .HasColumnType("uuid") - .HasColumnName("posts_id"); - - b.Property("TagsId") - .HasColumnType("uuid") - .HasColumnName("tags_id"); - - b.HasKey("PostsId", "TagsId") - .HasName("pk_post_tag_links"); - - b.HasIndex("TagsId") - .HasDatabaseName("ix_post_tag_links_tags_id"); - - b.ToTable("post_tag_links", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.AccountAuthFactor", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("AuthFactors") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_auth_factors_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.AccountConnection", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Connections") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_connections_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.AccountContact", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Contacts") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_contacts_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.ActionLog", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_action_logs_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Auth.Session", "Session") - .WithMany() - .HasForeignKey("SessionId") - .HasConstraintName("fk_action_logs_auth_sessions_session_id"); - - b.Navigation("Account"); - - b.Navigation("Session"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Badge", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Badges") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_badges_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.CheckInResult", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_check_in_results_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.MagicSpell", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .HasConstraintName("fk_magic_spells_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Notification", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_notifications_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.NotificationPushSubscription", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_notification_push_subscriptions_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Profile", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithOne("Profile") - .HasForeignKey("DysonNetwork.Sphere.Account.Profile", "AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_profiles_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Relationship", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("OutgoingRelationships") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_relationships_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Account.Account", "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.Sphere.Account.Status", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_statuses_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Challenge", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Challenges") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_challenges_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Session", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Sessions") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_sessions_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Auth.Challenge", "Challenge") - .WithMany() - .HasForeignKey("ChallengeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_sessions_auth_challenges_challenge_id"); - - b.Navigation("Account"); - - b.Navigation("Challenge"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatMember", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_members_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatRoom", "ChatRoom") - .WithMany("Members") - .HasForeignKey("ChatRoomId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_members_chat_rooms_chat_room_id"); - - b.Navigation("Account"); - - b.Navigation("ChatRoom"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatRoom", b => - { - b.HasOne("DysonNetwork.Sphere.Realm.Realm", "Realm") - .WithMany("ChatRooms") - .HasForeignKey("RealmId") - .HasConstraintName("fk_chat_rooms_realms_realm_id"); - - b.Navigation("Realm"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.Message", b => - { - b.HasOne("DysonNetwork.Sphere.Chat.ChatRoom", "ChatRoom") - .WithMany() - .HasForeignKey("ChatRoomId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_messages_chat_rooms_chat_room_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.Message", "ForwardedMessage") - .WithMany() - .HasForeignKey("ForwardedMessageId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_chat_messages_chat_messages_forwarded_message_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.Message", "RepliedMessage") - .WithMany() - .HasForeignKey("RepliedMessageId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_chat_messages_chat_messages_replied_message_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatMember", "Sender") - .WithMany() - .HasForeignKey("SenderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_messages_chat_members_sender_id"); - - b.Navigation("ChatRoom"); - - b.Navigation("ForwardedMessage"); - - b.Navigation("RepliedMessage"); - - b.Navigation("Sender"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.MessageReaction", b => - { - b.HasOne("DysonNetwork.Sphere.Chat.Message", "Message") - .WithMany("Reactions") - .HasForeignKey("MessageId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_reactions_chat_messages_message_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatMember", "Sender") - .WithMany() - .HasForeignKey("SenderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_reactions_chat_members_sender_id"); - - b.Navigation("Message"); - - b.Navigation("Sender"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.RealtimeCall", b => - { - b.HasOne("DysonNetwork.Sphere.Chat.ChatRoom", "Room") - .WithMany() - .HasForeignKey("RoomId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_realtime_call_chat_rooms_room_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatMember", "Sender") - .WithMany() - .HasForeignKey("SenderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_realtime_call_chat_members_sender_id"); - - b.Navigation("Room"); - - b.Navigation("Sender"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomApp", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Developer") - .WithMany() - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_custom_apps_publishers_publisher_id"); - - b.Navigation("Developer"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomAppSecret", b => - { - b.HasOne("DysonNetwork.Sphere.Developer.CustomApp", "App") - .WithMany() - .HasForeignKey("AppId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_custom_app_secrets_custom_apps_app_id"); - - b.Navigation("App"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroupMember", b => - { - b.HasOne("DysonNetwork.Sphere.Permission.PermissionGroup", "Group") - .WithMany("Members") - .HasForeignKey("GroupId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_permission_group_members_permission_groups_group_id"); - - b.Navigation("Group"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionNode", b => - { - b.HasOne("DysonNetwork.Sphere.Permission.PermissionGroup", "Group") - .WithMany("Nodes") - .HasForeignKey("GroupId") - .HasConstraintName("fk_permission_nodes_permission_groups_group_id"); - - b.Navigation("Group"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.Post", b => - { - b.HasOne("DysonNetwork.Sphere.Post.Post", "ForwardedPost") - .WithMany() - .HasForeignKey("ForwardedPostId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_posts_posts_forwarded_post_id"); - - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Posts") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_posts_publishers_publisher_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", "RepliedPost") - .WithMany() - .HasForeignKey("RepliedPostId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_posts_posts_replied_post_id"); - - b.Navigation("ForwardedPost"); - - b.Navigation("Publisher"); - - b.Navigation("RepliedPost"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostCollection", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Collections") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_collections_publishers_publisher_id"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostReaction", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_reactions_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", "Post") - .WithMany("Reactions") - .HasForeignKey("PostId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_reactions_posts_post_id"); - - b.Navigation("Account"); - - b.Navigation("Post"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.Publisher", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .HasConstraintName("fk_publishers_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Realm.Realm", "Realm") - .WithMany() - .HasForeignKey("RealmId") - .HasConstraintName("fk_publishers_realms_realm_id"); - - b.Navigation("Account"); - - b.Navigation("Realm"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherFeature", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany() - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_features_publishers_publisher_id"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherMember", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_members_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Members") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_members_publishers_publisher_id"); - - b.Navigation("Account"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherSubscription", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_subscriptions_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Subscriptions") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_subscriptions_publishers_publisher_id"); - - b.Navigation("Account"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.Realm", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_realms_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.RealmMember", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_realm_members_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Realm.Realm", "Realm") - .WithMany("Members") - .HasForeignKey("RealmId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_realm_members_realms_realm_id"); - - b.Navigation("Account"); - - b.Navigation("Realm"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.Sticker", b => - { - b.HasOne("DysonNetwork.Sphere.Sticker.StickerPack", "Pack") - .WithMany() - .HasForeignKey("PackId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_stickers_sticker_packs_pack_id"); - - b.Navigation("Pack"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.StickerPack", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany() - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_sticker_packs_publishers_publisher_id"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFile", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_files_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.Message", null) - .WithMany("OutdatedAttachments") - .HasForeignKey("MessageId") - .HasConstraintName("fk_files_chat_messages_message_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany("OutdatedAttachments") - .HasForeignKey("PostId") - .HasConstraintName("fk_files_posts_post_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFileReference", b => - { - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "File") - .WithMany() - .HasForeignKey("FileId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_file_references_files_file_id"); - - b.Navigation("File"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Order", b => - { - b.HasOne("DysonNetwork.Sphere.Developer.CustomApp", "IssuerApp") - .WithMany() - .HasForeignKey("IssuerAppId") - .HasConstraintName("fk_payment_orders_custom_apps_issuer_app_id"); - - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "PayeeWallet") - .WithMany() - .HasForeignKey("PayeeWalletId") - .HasConstraintName("fk_payment_orders_wallets_payee_wallet_id"); - - b.HasOne("DysonNetwork.Sphere.Wallet.Transaction", "Transaction") - .WithMany() - .HasForeignKey("TransactionId") - .HasConstraintName("fk_payment_orders_payment_transactions_transaction_id"); - - b.Navigation("IssuerApp"); - - b.Navigation("PayeeWallet"); - - b.Navigation("Transaction"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Subscription", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Subscriptions") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_wallet_subscriptions_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Wallet.Coupon", "Coupon") - .WithMany() - .HasForeignKey("CouponId") - .HasConstraintName("fk_wallet_subscriptions_wallet_coupons_coupon_id"); - - b.Navigation("Account"); - - b.Navigation("Coupon"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Transaction", b => - { - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "PayeeWallet") - .WithMany() - .HasForeignKey("PayeeWalletId") - .HasConstraintName("fk_payment_transactions_wallets_payee_wallet_id"); - - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "PayerWallet") - .WithMany() - .HasForeignKey("PayerWalletId") - .HasConstraintName("fk_payment_transactions_wallets_payer_wallet_id"); - - b.Navigation("PayeeWallet"); - - b.Navigation("PayerWallet"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Wallet", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_wallets_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.WalletPocket", b => - { - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "Wallet") - .WithMany("Pockets") - .HasForeignKey("WalletId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_wallet_pockets_wallets_wallet_id"); - - b.Navigation("Wallet"); - }); - - modelBuilder.Entity("PostPostCategory", b => - { - b.HasOne("DysonNetwork.Sphere.Post.PostCategory", null) - .WithMany() - .HasForeignKey("CategoriesId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_category_links_post_categories_categories_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany() - .HasForeignKey("PostsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_category_links_posts_posts_id"); - }); - - modelBuilder.Entity("PostPostCollection", b => - { - b.HasOne("DysonNetwork.Sphere.Post.PostCollection", null) - .WithMany() - .HasForeignKey("CollectionsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_collection_links_post_collections_collections_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany() - .HasForeignKey("PostsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_collection_links_posts_posts_id"); - }); - - modelBuilder.Entity("PostPostTag", b => - { - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany() - .HasForeignKey("PostsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_tag_links_posts_posts_id"); - - b.HasOne("DysonNetwork.Sphere.Post.PostTag", null) - .WithMany() - .HasForeignKey("TagsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_tag_links_post_tags_tags_id"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Account", 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"); - - b.Navigation("Subscriptions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatRoom", b => - { - b.Navigation("Members"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.Message", b => - { - b.Navigation("OutdatedAttachments"); - - b.Navigation("Reactions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroup", b => - { - b.Navigation("Members"); - - b.Navigation("Nodes"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.Post", b => - { - b.Navigation("OutdatedAttachments"); - - b.Navigation("Reactions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.Publisher", b => - { - b.Navigation("Collections"); - - b.Navigation("Members"); - - b.Navigation("Posts"); - - b.Navigation("Subscriptions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.Realm", b => - { - b.Navigation("ChatRooms"); - - b.Navigation("Members"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Wallet", b => - { - b.Navigation("Pockets"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/DysonNetwork.Sphere/Migrations/20250621191505_WalletOrderAppDX.cs b/DysonNetwork.Sphere/Migrations/20250621191505_WalletOrderAppDX.cs deleted file mode 100644 index bd433a9..0000000 --- a/DysonNetwork.Sphere/Migrations/20250621191505_WalletOrderAppDX.cs +++ /dev/null @@ -1,82 +0,0 @@ -using System; -using System.Collections.Generic; -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace DysonNetwork.Sphere.Migrations -{ - /// - public partial class WalletOrderAppDX : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropForeignKey( - name: "fk_payment_orders_wallets_payee_wallet_id", - table: "payment_orders"); - - migrationBuilder.AlterColumn( - name: "payee_wallet_id", - table: "payment_orders", - type: "uuid", - nullable: true, - oldClrType: typeof(Guid), - oldType: "uuid"); - - migrationBuilder.AddColumn( - name: "app_identifier", - table: "payment_orders", - type: "character varying(4096)", - maxLength: 4096, - nullable: true); - - migrationBuilder.AddColumn>( - name: "meta", - table: "payment_orders", - type: "jsonb", - nullable: true); - - migrationBuilder.AddForeignKey( - name: "fk_payment_orders_wallets_payee_wallet_id", - table: "payment_orders", - column: "payee_wallet_id", - principalTable: "wallets", - principalColumn: "id"); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropForeignKey( - name: "fk_payment_orders_wallets_payee_wallet_id", - table: "payment_orders"); - - migrationBuilder.DropColumn( - name: "app_identifier", - table: "payment_orders"); - - migrationBuilder.DropColumn( - name: "meta", - table: "payment_orders"); - - migrationBuilder.AlterColumn( - name: "payee_wallet_id", - table: "payment_orders", - type: "uuid", - nullable: false, - defaultValue: new Guid("00000000-0000-0000-0000-000000000000"), - oldClrType: typeof(Guid), - oldType: "uuid", - oldNullable: true); - - migrationBuilder.AddForeignKey( - name: "fk_payment_orders_wallets_payee_wallet_id", - table: "payment_orders", - column: "payee_wallet_id", - principalTable: "wallets", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - } - } -} diff --git a/DysonNetwork.Sphere/Migrations/20250624160304_DropActionLogSessionFk.Designer.cs b/DysonNetwork.Sphere/Migrations/20250624160304_DropActionLogSessionFk.Designer.cs deleted file mode 100644 index 67da21b..0000000 --- a/DysonNetwork.Sphere/Migrations/20250624160304_DropActionLogSessionFk.Designer.cs +++ /dev/null @@ -1,3623 +0,0 @@ -// -using System; -using System.Collections.Generic; -using System.Text.Json; -using DysonNetwork.Sphere; -using DysonNetwork.Sphere.Account; -using DysonNetwork.Sphere.Chat; -using DysonNetwork.Sphere.Storage; -using DysonNetwork.Sphere.Wallet; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using NetTopologySuite.Geometries; -using NodaTime; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; -using NpgsqlTypes; - -#nullable disable - -namespace DysonNetwork.Sphere.Migrations -{ - [DbContext(typeof(AppDatabase))] - [Migration("20250624160304_DropActionLogSessionFk")] - partial class DropActionLogSessionFk - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "9.0.3") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "postgis"); - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Account", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("ActivatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("activated_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("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("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.Sphere.Account.AccountAuthFactor", 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.Sphere.Account.AccountConnection", 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.Sphere.Account.AccountContact", 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("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.Sphere.Account.ActionLog", 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("geometry") - .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.Sphere.Account.Badge", 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.Sphere.Account.CheckInResult", 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("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.Sphere.Account.MagicSpell", 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.Sphere.Account.Notification", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("Content") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .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>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("Priority") - .HasColumnType("integer") - .HasColumnName("priority"); - - b.Property("Subtitle") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("subtitle"); - - b.Property("Title") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("title"); - - b.Property("Topic") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("topic"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("ViewedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("viewed_at"); - - b.HasKey("Id") - .HasName("pk_notifications"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_notifications_account_id"); - - b.ToTable("notifications", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.NotificationPushSubscription", 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(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("device_id"); - - b.Property("DeviceToken") - .IsRequired() - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("device_token"); - - b.Property("LastUsedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_used_at"); - - b.Property("Provider") - .HasColumnType("integer") - .HasColumnName("provider"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_notification_push_subscriptions"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_notification_push_subscriptions_account_id"); - - b.HasIndex("DeviceToken", "DeviceId", "AccountId") - .IsUnique() - .HasDatabaseName("ix_notification_push_subscriptions_device_token_device_id_acco"); - - b.ToTable("notification_push_subscriptions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Profile", 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("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_id"); - - 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("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("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - b.Property("Pronouns") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("pronouns"); - - b.Property("StellarMembership") - .HasColumnType("jsonb") - .HasColumnName("stellar_membership"); - - 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("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.Sphere.Account.Relationship", 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.Sphere.Account.Status", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - 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("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("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.Sphere.Auth.Challenge", 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") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("device_id"); - - 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("geometry") - .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("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_challenges"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_auth_challenges_account_id"); - - b.ToTable("auth_challenges", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Session", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("ChallengeId") - .HasColumnType("uuid") - .HasColumnName("challenge_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("Label") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("label"); - - b.Property("LastGrantedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_granted_at"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - 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.ToTable("auth_sessions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatMember", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("BreakUntil") - .HasColumnType("timestamp with time zone") - .HasColumnName("break_until"); - - b.Property("ChatRoomId") - .HasColumnType("uuid") - .HasColumnName("chat_room_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("IsBot") - .HasColumnType("boolean") - .HasColumnName("is_bot"); - - b.Property("JoinedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("joined_at"); - - b.Property("LastReadAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_read_at"); - - b.Property("LeaveAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("leave_at"); - - b.Property("Nick") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("nick"); - - b.Property("Notify") - .HasColumnType("integer") - .HasColumnName("notify"); - - b.Property("Role") - .HasColumnType("integer") - .HasColumnName("role"); - - b.Property("TimeoutCause") - .HasColumnType("jsonb") - .HasColumnName("timeout_cause"); - - b.Property("TimeoutUntil") - .HasColumnType("timestamp with time zone") - .HasColumnName("timeout_until"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_chat_members"); - - b.HasAlternateKey("ChatRoomId", "AccountId") - .HasName("ak_chat_members_chat_room_id_account_id"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_chat_members_account_id"); - - b.ToTable("chat_members", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatRoom", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("Background") - .HasColumnType("jsonb") - .HasColumnName("background"); - - b.Property("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_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("Description") - .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") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("Picture") - .HasColumnType("jsonb") - .HasColumnName("picture"); - - b.Property("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - b.Property("RealmId") - .HasColumnType("uuid") - .HasColumnName("realm_id"); - - b.Property("Type") - .HasColumnType("integer") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_chat_rooms"); - - b.HasIndex("RealmId") - .HasDatabaseName("ix_chat_rooms_realm_id"); - - b.ToTable("chat_rooms", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.Message", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property>("Attachments") - .IsRequired() - .HasColumnType("jsonb") - .HasColumnName("attachments"); - - b.Property("ChatRoomId") - .HasColumnType("uuid") - .HasColumnName("chat_room_id"); - - b.Property("Content") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .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("EditedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("edited_at"); - - b.Property("ForwardedMessageId") - .HasColumnType("uuid") - .HasColumnName("forwarded_message_id"); - - b.Property>("MembersMentioned") - .HasColumnType("jsonb") - .HasColumnName("members_mentioned"); - - b.Property>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("Nonce") - .IsRequired() - .HasMaxLength(36) - .HasColumnType("character varying(36)") - .HasColumnName("nonce"); - - b.Property("RepliedMessageId") - .HasColumnType("uuid") - .HasColumnName("replied_message_id"); - - b.Property("SenderId") - .HasColumnType("uuid") - .HasColumnName("sender_id"); - - 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_chat_messages"); - - b.HasIndex("ChatRoomId") - .HasDatabaseName("ix_chat_messages_chat_room_id"); - - b.HasIndex("ForwardedMessageId") - .HasDatabaseName("ix_chat_messages_forwarded_message_id"); - - b.HasIndex("RepliedMessageId") - .HasDatabaseName("ix_chat_messages_replied_message_id"); - - b.HasIndex("SenderId") - .HasDatabaseName("ix_chat_messages_sender_id"); - - b.ToTable("chat_messages", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.MessageReaction", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("Attitude") - .HasColumnType("integer") - .HasColumnName("attitude"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("MessageId") - .HasColumnType("uuid") - .HasColumnName("message_id"); - - b.Property("SenderId") - .HasColumnType("uuid") - .HasColumnName("sender_id"); - - b.Property("Symbol") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("symbol"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_chat_reactions"); - - b.HasIndex("MessageId") - .HasDatabaseName("ix_chat_reactions_message_id"); - - b.HasIndex("SenderId") - .HasDatabaseName("ix_chat_reactions_sender_id"); - - b.ToTable("chat_reactions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.RealtimeCall", 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("EndedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("ended_at"); - - b.Property("ProviderName") - .HasColumnType("text") - .HasColumnName("provider_name"); - - b.Property("RoomId") - .HasColumnType("uuid") - .HasColumnName("room_id"); - - b.Property("SenderId") - .HasColumnType("uuid") - .HasColumnName("sender_id"); - - b.Property("SessionId") - .HasColumnType("text") - .HasColumnName("session_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("UpstreamConfigJson") - .HasColumnType("jsonb") - .HasColumnName("upstream"); - - b.HasKey("Id") - .HasName("pk_chat_realtime_call"); - - b.HasIndex("RoomId") - .HasDatabaseName("ix_chat_realtime_call_room_id"); - - b.HasIndex("SenderId") - .HasDatabaseName("ix_chat_realtime_call_sender_id"); - - b.ToTable("chat_realtime_call", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomApp", 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("Name") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("slug"); - - b.Property("Status") - .HasColumnType("integer") - .HasColumnName("status"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("VerifiedAs") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("verified_as"); - - b.Property("VerifiedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("verified_at"); - - b.HasKey("Id") - .HasName("pk_custom_apps"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_custom_apps_publisher_id"); - - b.ToTable("custom_apps", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomAppSecret", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AppId") - .HasColumnType("uuid") - .HasColumnName("app_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("Remarks") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("remarks"); - - b.Property("Secret") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("secret"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_custom_app_secrets"); - - b.HasIndex("AppId") - .HasDatabaseName("ix_custom_app_secrets_app_id"); - - b.ToTable("custom_app_secrets", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroup", 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.Sphere.Permission.PermissionGroupMember", 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.Sphere.Permission.PermissionNode", 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("Area") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("area"); - - 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("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", "Area", "Actor") - .HasDatabaseName("ix_permission_nodes_key_area_actor"); - - b.ToTable("permission_nodes", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.Post", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property>("Attachments") - .IsRequired() - .HasColumnType("jsonb") - .HasColumnName("attachments"); - - b.Property("Content") - .HasColumnType("text") - .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("Description") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("Downvotes") - .HasColumnType("integer") - .HasColumnName("downvotes"); - - b.Property("EditedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("edited_at"); - - b.Property("ForwardedPostId") - .HasColumnType("uuid") - .HasColumnName("forwarded_post_id"); - - b.Property("Language") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("language"); - - b.Property>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("PublishedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("published_at"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("RepliedPostId") - .HasColumnType("uuid") - .HasColumnName("replied_post_id"); - - b.Property("SearchVector") - .IsRequired() - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("tsvector") - .HasColumnName("search_vector") - .HasAnnotation("Npgsql:TsVectorConfig", "simple") - .HasAnnotation("Npgsql:TsVectorProperties", new[] { "Title", "Description", "Content" }); - - b.Property("Title") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("title"); - - b.Property("Type") - .HasColumnType("integer") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("Upvotes") - .HasColumnType("integer") - .HasColumnName("upvotes"); - - b.Property("ViewsTotal") - .HasColumnType("integer") - .HasColumnName("views_total"); - - b.Property("ViewsUnique") - .HasColumnType("integer") - .HasColumnName("views_unique"); - - b.Property("Visibility") - .HasColumnType("integer") - .HasColumnName("visibility"); - - b.HasKey("Id") - .HasName("pk_posts"); - - b.HasIndex("ForwardedPostId") - .HasDatabaseName("ix_posts_forwarded_post_id"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_posts_publisher_id"); - - b.HasIndex("RepliedPostId") - .HasDatabaseName("ix_posts_replied_post_id"); - - b.HasIndex("SearchVector") - .HasDatabaseName("ix_posts_search_vector"); - - NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex("SearchVector"), "GIN"); - - b.ToTable("posts", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostCategory", 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("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("name"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_categories"); - - b.ToTable("post_categories", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostCollection", 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("Description") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("name"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_collections"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_post_collections_publisher_id"); - - b.ToTable("post_collections", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostReaction", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("Attitude") - .HasColumnType("integer") - .HasColumnName("attitude"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("PostId") - .HasColumnType("uuid") - .HasColumnName("post_id"); - - b.Property("Symbol") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("symbol"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_reactions"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_post_reactions_account_id"); - - b.HasIndex("PostId") - .HasDatabaseName("ix_post_reactions_post_id"); - - b.ToTable("post_reactions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostTag", 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("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("name"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_tags"); - - b.ToTable("post_tags", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.Publisher", 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("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_id"); - - b.Property("Bio") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("bio"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - 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("Picture") - .HasColumnType("jsonb") - .HasColumnName("picture"); - - b.Property("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - b.Property("RealmId") - .HasColumnType("uuid") - .HasColumnName("realm_id"); - - b.Property("Type") - .HasColumnType("integer") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("Verification") - .HasColumnType("jsonb") - .HasColumnName("verification"); - - b.HasKey("Id") - .HasName("pk_publishers"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_publishers_account_id"); - - b.HasIndex("Name") - .IsUnique() - .HasDatabaseName("ix_publishers_name"); - - b.HasIndex("RealmId") - .HasDatabaseName("ix_publishers_realm_id"); - - b.ToTable("publishers", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherFeature", 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("ExpiredAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expired_at"); - - b.Property("Flag") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("flag"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_publisher_features"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_publisher_features_publisher_id"); - - b.ToTable("publisher_features", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherMember", b => - { - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_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("Role") - .HasColumnType("integer") - .HasColumnName("role"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("PublisherId", "AccountId") - .HasName("pk_publisher_members"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_publisher_members_account_id"); - - b.ToTable("publisher_members", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherSubscription", 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("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("Status") - .HasColumnType("integer") - .HasColumnName("status"); - - b.Property("Tier") - .HasColumnType("integer") - .HasColumnName("tier"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_publisher_subscriptions"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_publisher_subscriptions_account_id"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_publisher_subscriptions_publisher_id"); - - b.ToTable("publisher_subscriptions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.Realm", 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("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_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("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("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - 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("AccountId") - .HasDatabaseName("ix_realms_account_id"); - - b.HasIndex("Slug") - .IsUnique() - .HasDatabaseName("ix_realms_slug"); - - b.ToTable("realms", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.RealmMember", 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.HasIndex("AccountId") - .HasDatabaseName("ix_realm_members_account_id"); - - b.ToTable("realm_members", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.Sticker", 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("Image") - .HasColumnType("jsonb") - .HasColumnName("image"); - - b.Property("ImageId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("image_id"); - - b.Property("PackId") - .HasColumnType("uuid") - .HasColumnName("pack_id"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_stickers"); - - b.HasIndex("PackId") - .HasDatabaseName("ix_stickers_pack_id"); - - b.HasIndex("Slug") - .HasDatabaseName("ix_stickers_slug"); - - b.ToTable("stickers", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.StickerPack", 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("Description") - .IsRequired() - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("Prefix") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("prefix"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_sticker_packs"); - - b.HasIndex("Prefix") - .IsUnique() - .HasDatabaseName("ix_sticker_packs_prefix"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_sticker_packs_publisher_id"); - - b.ToTable("sticker_packs", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFile", b => - { - b.Property("Id") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .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("Description") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property>("FileMeta") - .HasColumnType("jsonb") - .HasColumnName("file_meta"); - - b.Property("HasCompression") - .HasColumnType("boolean") - .HasColumnName("has_compression"); - - b.Property("Hash") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("hash"); - - b.Property("IsMarkedRecycle") - .HasColumnType("boolean") - .HasColumnName("is_marked_recycle"); - - b.Property("MessageId") - .HasColumnType("uuid") - .HasColumnName("message_id"); - - b.Property("MimeType") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("mime_type"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("PostId") - .HasColumnType("uuid") - .HasColumnName("post_id"); - - b.Property>("SensitiveMarks") - .HasColumnType("jsonb") - .HasColumnName("sensitive_marks"); - - b.Property("Size") - .HasColumnType("bigint") - .HasColumnName("size"); - - b.Property("StorageId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("storage_id"); - - b.Property("StorageUrl") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("storage_url"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("UploadedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("uploaded_at"); - - b.Property("UploadedTo") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("uploaded_to"); - - b.Property>("UserMeta") - .HasColumnType("jsonb") - .HasColumnName("user_meta"); - - b.HasKey("Id") - .HasName("pk_files"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_files_account_id"); - - b.HasIndex("MessageId") - .HasDatabaseName("ix_files_message_id"); - - b.HasIndex("PostId") - .HasDatabaseName("ix_files_post_id"); - - b.ToTable("files", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFileReference", 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("ExpiredAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expired_at"); - - b.Property("FileId") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("file_id"); - - b.Property("ResourceId") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("resource_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("Usage") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("usage"); - - b.HasKey("Id") - .HasName("pk_file_references"); - - b.HasIndex("FileId") - .HasDatabaseName("ix_file_references_file_id"); - - b.ToTable("file_references", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Coupon", 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.Sphere.Wallet.Order", 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("IssuerAppId") - .HasColumnType("uuid") - .HasColumnName("issuer_app_id"); - - b.Property>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("PayeeWalletId") - .HasColumnType("uuid") - .HasColumnName("payee_wallet_id"); - - 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("IssuerAppId") - .HasDatabaseName("ix_payment_orders_issuer_app_id"); - - 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.Sphere.Wallet.Subscription", 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.ToTable("wallet_subscriptions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Transaction", 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.Sphere.Wallet.Wallet", 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.Sphere.Wallet.WalletPocket", 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("PostPostCategory", b => - { - b.Property("CategoriesId") - .HasColumnType("uuid") - .HasColumnName("categories_id"); - - b.Property("PostsId") - .HasColumnType("uuid") - .HasColumnName("posts_id"); - - b.HasKey("CategoriesId", "PostsId") - .HasName("pk_post_category_links"); - - b.HasIndex("PostsId") - .HasDatabaseName("ix_post_category_links_posts_id"); - - b.ToTable("post_category_links", (string)null); - }); - - modelBuilder.Entity("PostPostCollection", b => - { - b.Property("CollectionsId") - .HasColumnType("uuid") - .HasColumnName("collections_id"); - - b.Property("PostsId") - .HasColumnType("uuid") - .HasColumnName("posts_id"); - - b.HasKey("CollectionsId", "PostsId") - .HasName("pk_post_collection_links"); - - b.HasIndex("PostsId") - .HasDatabaseName("ix_post_collection_links_posts_id"); - - b.ToTable("post_collection_links", (string)null); - }); - - modelBuilder.Entity("PostPostTag", b => - { - b.Property("PostsId") - .HasColumnType("uuid") - .HasColumnName("posts_id"); - - b.Property("TagsId") - .HasColumnType("uuid") - .HasColumnName("tags_id"); - - b.HasKey("PostsId", "TagsId") - .HasName("pk_post_tag_links"); - - b.HasIndex("TagsId") - .HasDatabaseName("ix_post_tag_links_tags_id"); - - b.ToTable("post_tag_links", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.AccountAuthFactor", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("AuthFactors") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_auth_factors_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.AccountConnection", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Connections") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_connections_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.AccountContact", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Contacts") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_contacts_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.ActionLog", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_action_logs_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Badge", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Badges") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_badges_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.CheckInResult", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_check_in_results_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.MagicSpell", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .HasConstraintName("fk_magic_spells_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Notification", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_notifications_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.NotificationPushSubscription", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_notification_push_subscriptions_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Profile", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithOne("Profile") - .HasForeignKey("DysonNetwork.Sphere.Account.Profile", "AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_profiles_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Relationship", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("OutgoingRelationships") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_relationships_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Account.Account", "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.Sphere.Account.Status", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_statuses_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Challenge", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Challenges") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_challenges_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Session", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Sessions") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_sessions_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Auth.Challenge", "Challenge") - .WithMany() - .HasForeignKey("ChallengeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_sessions_auth_challenges_challenge_id"); - - b.Navigation("Account"); - - b.Navigation("Challenge"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatMember", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_members_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatRoom", "ChatRoom") - .WithMany("Members") - .HasForeignKey("ChatRoomId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_members_chat_rooms_chat_room_id"); - - b.Navigation("Account"); - - b.Navigation("ChatRoom"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatRoom", b => - { - b.HasOne("DysonNetwork.Sphere.Realm.Realm", "Realm") - .WithMany("ChatRooms") - .HasForeignKey("RealmId") - .HasConstraintName("fk_chat_rooms_realms_realm_id"); - - b.Navigation("Realm"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.Message", b => - { - b.HasOne("DysonNetwork.Sphere.Chat.ChatRoom", "ChatRoom") - .WithMany() - .HasForeignKey("ChatRoomId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_messages_chat_rooms_chat_room_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.Message", "ForwardedMessage") - .WithMany() - .HasForeignKey("ForwardedMessageId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_chat_messages_chat_messages_forwarded_message_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.Message", "RepliedMessage") - .WithMany() - .HasForeignKey("RepliedMessageId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_chat_messages_chat_messages_replied_message_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatMember", "Sender") - .WithMany() - .HasForeignKey("SenderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_messages_chat_members_sender_id"); - - b.Navigation("ChatRoom"); - - b.Navigation("ForwardedMessage"); - - b.Navigation("RepliedMessage"); - - b.Navigation("Sender"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.MessageReaction", b => - { - b.HasOne("DysonNetwork.Sphere.Chat.Message", "Message") - .WithMany("Reactions") - .HasForeignKey("MessageId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_reactions_chat_messages_message_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatMember", "Sender") - .WithMany() - .HasForeignKey("SenderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_reactions_chat_members_sender_id"); - - b.Navigation("Message"); - - b.Navigation("Sender"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.RealtimeCall", b => - { - b.HasOne("DysonNetwork.Sphere.Chat.ChatRoom", "Room") - .WithMany() - .HasForeignKey("RoomId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_realtime_call_chat_rooms_room_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatMember", "Sender") - .WithMany() - .HasForeignKey("SenderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_realtime_call_chat_members_sender_id"); - - b.Navigation("Room"); - - b.Navigation("Sender"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomApp", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Developer") - .WithMany() - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_custom_apps_publishers_publisher_id"); - - b.Navigation("Developer"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomAppSecret", b => - { - b.HasOne("DysonNetwork.Sphere.Developer.CustomApp", "App") - .WithMany() - .HasForeignKey("AppId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_custom_app_secrets_custom_apps_app_id"); - - b.Navigation("App"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroupMember", b => - { - b.HasOne("DysonNetwork.Sphere.Permission.PermissionGroup", "Group") - .WithMany("Members") - .HasForeignKey("GroupId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_permission_group_members_permission_groups_group_id"); - - b.Navigation("Group"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionNode", b => - { - b.HasOne("DysonNetwork.Sphere.Permission.PermissionGroup", "Group") - .WithMany("Nodes") - .HasForeignKey("GroupId") - .HasConstraintName("fk_permission_nodes_permission_groups_group_id"); - - b.Navigation("Group"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.Post", b => - { - b.HasOne("DysonNetwork.Sphere.Post.Post", "ForwardedPost") - .WithMany() - .HasForeignKey("ForwardedPostId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_posts_posts_forwarded_post_id"); - - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Posts") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_posts_publishers_publisher_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", "RepliedPost") - .WithMany() - .HasForeignKey("RepliedPostId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_posts_posts_replied_post_id"); - - b.Navigation("ForwardedPost"); - - b.Navigation("Publisher"); - - b.Navigation("RepliedPost"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostCollection", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Collections") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_collections_publishers_publisher_id"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostReaction", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_reactions_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", "Post") - .WithMany("Reactions") - .HasForeignKey("PostId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_reactions_posts_post_id"); - - b.Navigation("Account"); - - b.Navigation("Post"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.Publisher", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .HasConstraintName("fk_publishers_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Realm.Realm", "Realm") - .WithMany() - .HasForeignKey("RealmId") - .HasConstraintName("fk_publishers_realms_realm_id"); - - b.Navigation("Account"); - - b.Navigation("Realm"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherFeature", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany() - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_features_publishers_publisher_id"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherMember", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_members_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Members") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_members_publishers_publisher_id"); - - b.Navigation("Account"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherSubscription", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_subscriptions_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Subscriptions") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_subscriptions_publishers_publisher_id"); - - b.Navigation("Account"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.Realm", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_realms_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.RealmMember", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_realm_members_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Realm.Realm", "Realm") - .WithMany("Members") - .HasForeignKey("RealmId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_realm_members_realms_realm_id"); - - b.Navigation("Account"); - - b.Navigation("Realm"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.Sticker", b => - { - b.HasOne("DysonNetwork.Sphere.Sticker.StickerPack", "Pack") - .WithMany() - .HasForeignKey("PackId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_stickers_sticker_packs_pack_id"); - - b.Navigation("Pack"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.StickerPack", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany() - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_sticker_packs_publishers_publisher_id"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFile", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_files_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.Message", null) - .WithMany("OutdatedAttachments") - .HasForeignKey("MessageId") - .HasConstraintName("fk_files_chat_messages_message_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany("OutdatedAttachments") - .HasForeignKey("PostId") - .HasConstraintName("fk_files_posts_post_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFileReference", b => - { - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "File") - .WithMany() - .HasForeignKey("FileId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_file_references_files_file_id"); - - b.Navigation("File"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Order", b => - { - b.HasOne("DysonNetwork.Sphere.Developer.CustomApp", "IssuerApp") - .WithMany() - .HasForeignKey("IssuerAppId") - .HasConstraintName("fk_payment_orders_custom_apps_issuer_app_id"); - - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "PayeeWallet") - .WithMany() - .HasForeignKey("PayeeWalletId") - .HasConstraintName("fk_payment_orders_wallets_payee_wallet_id"); - - b.HasOne("DysonNetwork.Sphere.Wallet.Transaction", "Transaction") - .WithMany() - .HasForeignKey("TransactionId") - .HasConstraintName("fk_payment_orders_payment_transactions_transaction_id"); - - b.Navigation("IssuerApp"); - - b.Navigation("PayeeWallet"); - - b.Navigation("Transaction"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Subscription", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Subscriptions") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_wallet_subscriptions_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Wallet.Coupon", "Coupon") - .WithMany() - .HasForeignKey("CouponId") - .HasConstraintName("fk_wallet_subscriptions_wallet_coupons_coupon_id"); - - b.Navigation("Account"); - - b.Navigation("Coupon"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Transaction", b => - { - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "PayeeWallet") - .WithMany() - .HasForeignKey("PayeeWalletId") - .HasConstraintName("fk_payment_transactions_wallets_payee_wallet_id"); - - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "PayerWallet") - .WithMany() - .HasForeignKey("PayerWalletId") - .HasConstraintName("fk_payment_transactions_wallets_payer_wallet_id"); - - b.Navigation("PayeeWallet"); - - b.Navigation("PayerWallet"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Wallet", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_wallets_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.WalletPocket", b => - { - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "Wallet") - .WithMany("Pockets") - .HasForeignKey("WalletId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_wallet_pockets_wallets_wallet_id"); - - b.Navigation("Wallet"); - }); - - modelBuilder.Entity("PostPostCategory", b => - { - b.HasOne("DysonNetwork.Sphere.Post.PostCategory", null) - .WithMany() - .HasForeignKey("CategoriesId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_category_links_post_categories_categories_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany() - .HasForeignKey("PostsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_category_links_posts_posts_id"); - }); - - modelBuilder.Entity("PostPostCollection", b => - { - b.HasOne("DysonNetwork.Sphere.Post.PostCollection", null) - .WithMany() - .HasForeignKey("CollectionsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_collection_links_post_collections_collections_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany() - .HasForeignKey("PostsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_collection_links_posts_posts_id"); - }); - - modelBuilder.Entity("PostPostTag", b => - { - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany() - .HasForeignKey("PostsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_tag_links_posts_posts_id"); - - b.HasOne("DysonNetwork.Sphere.Post.PostTag", null) - .WithMany() - .HasForeignKey("TagsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_tag_links_post_tags_tags_id"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Account", 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"); - - b.Navigation("Subscriptions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatRoom", b => - { - b.Navigation("Members"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.Message", b => - { - b.Navigation("OutdatedAttachments"); - - b.Navigation("Reactions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroup", b => - { - b.Navigation("Members"); - - b.Navigation("Nodes"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.Post", b => - { - b.Navigation("OutdatedAttachments"); - - b.Navigation("Reactions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.Publisher", b => - { - b.Navigation("Collections"); - - b.Navigation("Members"); - - b.Navigation("Posts"); - - b.Navigation("Subscriptions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.Realm", b => - { - b.Navigation("ChatRooms"); - - b.Navigation("Members"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Wallet", b => - { - b.Navigation("Pockets"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/DysonNetwork.Sphere/Migrations/20250624160304_DropActionLogSessionFk.cs b/DysonNetwork.Sphere/Migrations/20250624160304_DropActionLogSessionFk.cs deleted file mode 100644 index ca67273..0000000 --- a/DysonNetwork.Sphere/Migrations/20250624160304_DropActionLogSessionFk.cs +++ /dev/null @@ -1,38 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace DysonNetwork.Sphere.Migrations -{ - /// - public partial class DropActionLogSessionFk : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropForeignKey( - name: "fk_action_logs_auth_sessions_session_id", - table: "action_logs"); - - migrationBuilder.DropIndex( - name: "ix_action_logs_session_id", - table: "action_logs"); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateIndex( - name: "ix_action_logs_session_id", - table: "action_logs", - column: "session_id"); - - migrationBuilder.AddForeignKey( - name: "fk_action_logs_auth_sessions_session_id", - table: "action_logs", - column: "session_id", - principalTable: "auth_sessions", - principalColumn: "id"); - } - } -} diff --git a/DysonNetwork.Sphere/Migrations/20250625150644_SafetyAbuseReport.Designer.cs b/DysonNetwork.Sphere/Migrations/20250625150644_SafetyAbuseReport.Designer.cs deleted file mode 100644 index 4d0d6e3..0000000 --- a/DysonNetwork.Sphere/Migrations/20250625150644_SafetyAbuseReport.Designer.cs +++ /dev/null @@ -1,3696 +0,0 @@ -// -using System; -using System.Collections.Generic; -using System.Text.Json; -using DysonNetwork.Sphere; -using DysonNetwork.Sphere.Account; -using DysonNetwork.Sphere.Chat; -using DysonNetwork.Sphere.Storage; -using DysonNetwork.Sphere.Wallet; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using NetTopologySuite.Geometries; -using NodaTime; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; -using NpgsqlTypes; - -#nullable disable - -namespace DysonNetwork.Sphere.Migrations -{ - [DbContext(typeof(AppDatabase))] - [Migration("20250625150644_SafetyAbuseReport")] - partial class SafetyAbuseReport - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "9.0.3") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "postgis"); - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.AbuseReport", 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.Sphere.Account.Account", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("ActivatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("activated_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("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("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.Sphere.Account.AccountAuthFactor", 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.Sphere.Account.AccountConnection", 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.Sphere.Account.AccountContact", 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("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.Sphere.Account.ActionLog", 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("geometry") - .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.Sphere.Account.Badge", 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.Sphere.Account.CheckInResult", 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("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.Sphere.Account.MagicSpell", 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.Sphere.Account.Notification", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("Content") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .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>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("Priority") - .HasColumnType("integer") - .HasColumnName("priority"); - - b.Property("Subtitle") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("subtitle"); - - b.Property("Title") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("title"); - - b.Property("Topic") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("topic"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("ViewedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("viewed_at"); - - b.HasKey("Id") - .HasName("pk_notifications"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_notifications_account_id"); - - b.ToTable("notifications", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.NotificationPushSubscription", 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(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("device_id"); - - b.Property("DeviceToken") - .IsRequired() - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("device_token"); - - b.Property("LastUsedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_used_at"); - - b.Property("Provider") - .HasColumnType("integer") - .HasColumnName("provider"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_notification_push_subscriptions"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_notification_push_subscriptions_account_id"); - - b.HasIndex("DeviceToken", "DeviceId", "AccountId") - .IsUnique() - .HasDatabaseName("ix_notification_push_subscriptions_device_token_device_id_acco"); - - b.ToTable("notification_push_subscriptions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Profile", 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("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_id"); - - 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("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("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - b.Property("Pronouns") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("pronouns"); - - b.Property("StellarMembership") - .HasColumnType("jsonb") - .HasColumnName("stellar_membership"); - - 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("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.Sphere.Account.Relationship", 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.Sphere.Account.Status", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - 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("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("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.Sphere.Auth.Challenge", 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") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("device_id"); - - 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("geometry") - .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("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_challenges"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_auth_challenges_account_id"); - - b.ToTable("auth_challenges", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Session", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("ChallengeId") - .HasColumnType("uuid") - .HasColumnName("challenge_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("Label") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("label"); - - b.Property("LastGrantedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_granted_at"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - 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.ToTable("auth_sessions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatMember", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("BreakUntil") - .HasColumnType("timestamp with time zone") - .HasColumnName("break_until"); - - b.Property("ChatRoomId") - .HasColumnType("uuid") - .HasColumnName("chat_room_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("IsBot") - .HasColumnType("boolean") - .HasColumnName("is_bot"); - - b.Property("JoinedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("joined_at"); - - b.Property("LastReadAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_read_at"); - - b.Property("LeaveAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("leave_at"); - - b.Property("Nick") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("nick"); - - b.Property("Notify") - .HasColumnType("integer") - .HasColumnName("notify"); - - b.Property("Role") - .HasColumnType("integer") - .HasColumnName("role"); - - b.Property("TimeoutCause") - .HasColumnType("jsonb") - .HasColumnName("timeout_cause"); - - b.Property("TimeoutUntil") - .HasColumnType("timestamp with time zone") - .HasColumnName("timeout_until"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_chat_members"); - - b.HasAlternateKey("ChatRoomId", "AccountId") - .HasName("ak_chat_members_chat_room_id_account_id"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_chat_members_account_id"); - - b.ToTable("chat_members", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatRoom", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("Background") - .HasColumnType("jsonb") - .HasColumnName("background"); - - b.Property("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_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("Description") - .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") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("Picture") - .HasColumnType("jsonb") - .HasColumnName("picture"); - - b.Property("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - b.Property("RealmId") - .HasColumnType("uuid") - .HasColumnName("realm_id"); - - b.Property("Type") - .HasColumnType("integer") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_chat_rooms"); - - b.HasIndex("RealmId") - .HasDatabaseName("ix_chat_rooms_realm_id"); - - b.ToTable("chat_rooms", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.Message", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property>("Attachments") - .IsRequired() - .HasColumnType("jsonb") - .HasColumnName("attachments"); - - b.Property("ChatRoomId") - .HasColumnType("uuid") - .HasColumnName("chat_room_id"); - - b.Property("Content") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .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("EditedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("edited_at"); - - b.Property("ForwardedMessageId") - .HasColumnType("uuid") - .HasColumnName("forwarded_message_id"); - - b.Property>("MembersMentioned") - .HasColumnType("jsonb") - .HasColumnName("members_mentioned"); - - b.Property>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("Nonce") - .IsRequired() - .HasMaxLength(36) - .HasColumnType("character varying(36)") - .HasColumnName("nonce"); - - b.Property("RepliedMessageId") - .HasColumnType("uuid") - .HasColumnName("replied_message_id"); - - b.Property("SenderId") - .HasColumnType("uuid") - .HasColumnName("sender_id"); - - 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_chat_messages"); - - b.HasIndex("ChatRoomId") - .HasDatabaseName("ix_chat_messages_chat_room_id"); - - b.HasIndex("ForwardedMessageId") - .HasDatabaseName("ix_chat_messages_forwarded_message_id"); - - b.HasIndex("RepliedMessageId") - .HasDatabaseName("ix_chat_messages_replied_message_id"); - - b.HasIndex("SenderId") - .HasDatabaseName("ix_chat_messages_sender_id"); - - b.ToTable("chat_messages", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.MessageReaction", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("Attitude") - .HasColumnType("integer") - .HasColumnName("attitude"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("MessageId") - .HasColumnType("uuid") - .HasColumnName("message_id"); - - b.Property("SenderId") - .HasColumnType("uuid") - .HasColumnName("sender_id"); - - b.Property("Symbol") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("symbol"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_chat_reactions"); - - b.HasIndex("MessageId") - .HasDatabaseName("ix_chat_reactions_message_id"); - - b.HasIndex("SenderId") - .HasDatabaseName("ix_chat_reactions_sender_id"); - - b.ToTable("chat_reactions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.RealtimeCall", 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("EndedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("ended_at"); - - b.Property("ProviderName") - .HasColumnType("text") - .HasColumnName("provider_name"); - - b.Property("RoomId") - .HasColumnType("uuid") - .HasColumnName("room_id"); - - b.Property("SenderId") - .HasColumnType("uuid") - .HasColumnName("sender_id"); - - b.Property("SessionId") - .HasColumnType("text") - .HasColumnName("session_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("UpstreamConfigJson") - .HasColumnType("jsonb") - .HasColumnName("upstream"); - - b.HasKey("Id") - .HasName("pk_chat_realtime_call"); - - b.HasIndex("RoomId") - .HasDatabaseName("ix_chat_realtime_call_room_id"); - - b.HasIndex("SenderId") - .HasDatabaseName("ix_chat_realtime_call_sender_id"); - - b.ToTable("chat_realtime_call", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomApp", 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("Name") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("slug"); - - b.Property("Status") - .HasColumnType("integer") - .HasColumnName("status"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("VerifiedAs") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("verified_as"); - - b.Property("VerifiedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("verified_at"); - - b.HasKey("Id") - .HasName("pk_custom_apps"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_custom_apps_publisher_id"); - - b.ToTable("custom_apps", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomAppSecret", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AppId") - .HasColumnType("uuid") - .HasColumnName("app_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("Remarks") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("remarks"); - - b.Property("Secret") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("secret"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_custom_app_secrets"); - - b.HasIndex("AppId") - .HasDatabaseName("ix_custom_app_secrets_app_id"); - - b.ToTable("custom_app_secrets", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroup", 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.Sphere.Permission.PermissionGroupMember", 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.Sphere.Permission.PermissionNode", 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("Area") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("area"); - - 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("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", "Area", "Actor") - .HasDatabaseName("ix_permission_nodes_key_area_actor"); - - b.ToTable("permission_nodes", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.Post", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property>("Attachments") - .IsRequired() - .HasColumnType("jsonb") - .HasColumnName("attachments"); - - b.Property("Content") - .HasColumnType("text") - .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("Description") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("Downvotes") - .HasColumnType("integer") - .HasColumnName("downvotes"); - - b.Property("EditedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("edited_at"); - - b.Property("ForwardedPostId") - .HasColumnType("uuid") - .HasColumnName("forwarded_post_id"); - - b.Property("Language") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("language"); - - b.Property>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("PublishedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("published_at"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("RepliedPostId") - .HasColumnType("uuid") - .HasColumnName("replied_post_id"); - - b.Property("SearchVector") - .IsRequired() - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("tsvector") - .HasColumnName("search_vector") - .HasAnnotation("Npgsql:TsVectorConfig", "simple") - .HasAnnotation("Npgsql:TsVectorProperties", new[] { "Title", "Description", "Content" }); - - b.Property>("SensitiveMarks") - .HasColumnType("jsonb") - .HasColumnName("sensitive_marks"); - - b.Property("Title") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("title"); - - b.Property("Type") - .HasColumnType("integer") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("Upvotes") - .HasColumnType("integer") - .HasColumnName("upvotes"); - - b.Property("ViewsTotal") - .HasColumnType("integer") - .HasColumnName("views_total"); - - b.Property("ViewsUnique") - .HasColumnType("integer") - .HasColumnName("views_unique"); - - b.Property("Visibility") - .HasColumnType("integer") - .HasColumnName("visibility"); - - b.HasKey("Id") - .HasName("pk_posts"); - - b.HasIndex("ForwardedPostId") - .HasDatabaseName("ix_posts_forwarded_post_id"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_posts_publisher_id"); - - b.HasIndex("RepliedPostId") - .HasDatabaseName("ix_posts_replied_post_id"); - - b.HasIndex("SearchVector") - .HasDatabaseName("ix_posts_search_vector"); - - NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex("SearchVector"), "GIN"); - - b.ToTable("posts", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostCategory", 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("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("name"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_categories"); - - b.ToTable("post_categories", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostCollection", 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("Description") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("name"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_collections"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_post_collections_publisher_id"); - - b.ToTable("post_collections", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostReaction", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("Attitude") - .HasColumnType("integer") - .HasColumnName("attitude"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("PostId") - .HasColumnType("uuid") - .HasColumnName("post_id"); - - b.Property("Symbol") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("symbol"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_reactions"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_post_reactions_account_id"); - - b.HasIndex("PostId") - .HasDatabaseName("ix_post_reactions_post_id"); - - b.ToTable("post_reactions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostTag", 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("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("name"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_tags"); - - b.ToTable("post_tags", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.Publisher", 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("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_id"); - - b.Property("Bio") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("bio"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - 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("Picture") - .HasColumnType("jsonb") - .HasColumnName("picture"); - - b.Property("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - b.Property("RealmId") - .HasColumnType("uuid") - .HasColumnName("realm_id"); - - b.Property("Type") - .HasColumnType("integer") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("Verification") - .HasColumnType("jsonb") - .HasColumnName("verification"); - - b.HasKey("Id") - .HasName("pk_publishers"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_publishers_account_id"); - - b.HasIndex("Name") - .IsUnique() - .HasDatabaseName("ix_publishers_name"); - - b.HasIndex("RealmId") - .HasDatabaseName("ix_publishers_realm_id"); - - b.ToTable("publishers", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherFeature", 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("ExpiredAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expired_at"); - - b.Property("Flag") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("flag"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_publisher_features"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_publisher_features_publisher_id"); - - b.ToTable("publisher_features", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherMember", b => - { - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_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("Role") - .HasColumnType("integer") - .HasColumnName("role"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("PublisherId", "AccountId") - .HasName("pk_publisher_members"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_publisher_members_account_id"); - - b.ToTable("publisher_members", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherSubscription", 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("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("Status") - .HasColumnType("integer") - .HasColumnName("status"); - - b.Property("Tier") - .HasColumnType("integer") - .HasColumnName("tier"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_publisher_subscriptions"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_publisher_subscriptions_account_id"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_publisher_subscriptions_publisher_id"); - - b.ToTable("publisher_subscriptions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.Realm", 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("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_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("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("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - 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("AccountId") - .HasDatabaseName("ix_realms_account_id"); - - b.HasIndex("Slug") - .IsUnique() - .HasDatabaseName("ix_realms_slug"); - - b.ToTable("realms", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.RealmMember", 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.HasIndex("AccountId") - .HasDatabaseName("ix_realm_members_account_id"); - - b.ToTable("realm_members", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.Sticker", 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("Image") - .HasColumnType("jsonb") - .HasColumnName("image"); - - b.Property("ImageId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("image_id"); - - b.Property("PackId") - .HasColumnType("uuid") - .HasColumnName("pack_id"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_stickers"); - - b.HasIndex("PackId") - .HasDatabaseName("ix_stickers_pack_id"); - - b.HasIndex("Slug") - .HasDatabaseName("ix_stickers_slug"); - - b.ToTable("stickers", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.StickerPack", 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("Description") - .IsRequired() - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("Prefix") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("prefix"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_sticker_packs"); - - b.HasIndex("Prefix") - .IsUnique() - .HasDatabaseName("ix_sticker_packs_prefix"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_sticker_packs_publisher_id"); - - b.ToTable("sticker_packs", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFile", b => - { - b.Property("Id") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .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("Description") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property>("FileMeta") - .HasColumnType("jsonb") - .HasColumnName("file_meta"); - - b.Property("HasCompression") - .HasColumnType("boolean") - .HasColumnName("has_compression"); - - b.Property("Hash") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("hash"); - - b.Property("IsMarkedRecycle") - .HasColumnType("boolean") - .HasColumnName("is_marked_recycle"); - - b.Property("MessageId") - .HasColumnType("uuid") - .HasColumnName("message_id"); - - b.Property("MimeType") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("mime_type"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("PostId") - .HasColumnType("uuid") - .HasColumnName("post_id"); - - b.Property>("SensitiveMarks") - .HasColumnType("jsonb") - .HasColumnName("sensitive_marks"); - - b.Property("Size") - .HasColumnType("bigint") - .HasColumnName("size"); - - b.Property("StorageId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("storage_id"); - - b.Property("StorageUrl") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("storage_url"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("UploadedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("uploaded_at"); - - b.Property("UploadedTo") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("uploaded_to"); - - b.Property>("UserMeta") - .HasColumnType("jsonb") - .HasColumnName("user_meta"); - - b.HasKey("Id") - .HasName("pk_files"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_files_account_id"); - - b.HasIndex("MessageId") - .HasDatabaseName("ix_files_message_id"); - - b.HasIndex("PostId") - .HasDatabaseName("ix_files_post_id"); - - b.ToTable("files", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFileReference", 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("ExpiredAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expired_at"); - - b.Property("FileId") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("file_id"); - - b.Property("ResourceId") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("resource_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("Usage") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("usage"); - - b.HasKey("Id") - .HasName("pk_file_references"); - - b.HasIndex("FileId") - .HasDatabaseName("ix_file_references_file_id"); - - b.ToTable("file_references", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Coupon", 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.Sphere.Wallet.Order", 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("IssuerAppId") - .HasColumnType("uuid") - .HasColumnName("issuer_app_id"); - - b.Property>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("PayeeWalletId") - .HasColumnType("uuid") - .HasColumnName("payee_wallet_id"); - - 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("IssuerAppId") - .HasDatabaseName("ix_payment_orders_issuer_app_id"); - - 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.Sphere.Wallet.Subscription", 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.ToTable("wallet_subscriptions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Transaction", 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.Sphere.Wallet.Wallet", 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.Sphere.Wallet.WalletPocket", 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("PostPostCategory", b => - { - b.Property("CategoriesId") - .HasColumnType("uuid") - .HasColumnName("categories_id"); - - b.Property("PostsId") - .HasColumnType("uuid") - .HasColumnName("posts_id"); - - b.HasKey("CategoriesId", "PostsId") - .HasName("pk_post_category_links"); - - b.HasIndex("PostsId") - .HasDatabaseName("ix_post_category_links_posts_id"); - - b.ToTable("post_category_links", (string)null); - }); - - modelBuilder.Entity("PostPostCollection", b => - { - b.Property("CollectionsId") - .HasColumnType("uuid") - .HasColumnName("collections_id"); - - b.Property("PostsId") - .HasColumnType("uuid") - .HasColumnName("posts_id"); - - b.HasKey("CollectionsId", "PostsId") - .HasName("pk_post_collection_links"); - - b.HasIndex("PostsId") - .HasDatabaseName("ix_post_collection_links_posts_id"); - - b.ToTable("post_collection_links", (string)null); - }); - - modelBuilder.Entity("PostPostTag", b => - { - b.Property("PostsId") - .HasColumnType("uuid") - .HasColumnName("posts_id"); - - b.Property("TagsId") - .HasColumnType("uuid") - .HasColumnName("tags_id"); - - b.HasKey("PostsId", "TagsId") - .HasName("pk_post_tag_links"); - - b.HasIndex("TagsId") - .HasDatabaseName("ix_post_tag_links_tags_id"); - - b.ToTable("post_tag_links", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.AbuseReport", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_abuse_reports_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.AccountAuthFactor", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("AuthFactors") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_auth_factors_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.AccountConnection", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Connections") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_connections_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.AccountContact", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Contacts") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_contacts_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.ActionLog", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_action_logs_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Badge", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Badges") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_badges_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.CheckInResult", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_check_in_results_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.MagicSpell", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .HasConstraintName("fk_magic_spells_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Notification", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_notifications_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.NotificationPushSubscription", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_notification_push_subscriptions_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Profile", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithOne("Profile") - .HasForeignKey("DysonNetwork.Sphere.Account.Profile", "AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_profiles_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Relationship", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("OutgoingRelationships") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_relationships_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Account.Account", "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.Sphere.Account.Status", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_statuses_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Challenge", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Challenges") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_challenges_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Session", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Sessions") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_sessions_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Auth.Challenge", "Challenge") - .WithMany() - .HasForeignKey("ChallengeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_sessions_auth_challenges_challenge_id"); - - b.Navigation("Account"); - - b.Navigation("Challenge"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatMember", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_members_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatRoom", "ChatRoom") - .WithMany("Members") - .HasForeignKey("ChatRoomId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_members_chat_rooms_chat_room_id"); - - b.Navigation("Account"); - - b.Navigation("ChatRoom"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatRoom", b => - { - b.HasOne("DysonNetwork.Sphere.Realm.Realm", "Realm") - .WithMany("ChatRooms") - .HasForeignKey("RealmId") - .HasConstraintName("fk_chat_rooms_realms_realm_id"); - - b.Navigation("Realm"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.Message", b => - { - b.HasOne("DysonNetwork.Sphere.Chat.ChatRoom", "ChatRoom") - .WithMany() - .HasForeignKey("ChatRoomId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_messages_chat_rooms_chat_room_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.Message", "ForwardedMessage") - .WithMany() - .HasForeignKey("ForwardedMessageId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_chat_messages_chat_messages_forwarded_message_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.Message", "RepliedMessage") - .WithMany() - .HasForeignKey("RepliedMessageId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_chat_messages_chat_messages_replied_message_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatMember", "Sender") - .WithMany() - .HasForeignKey("SenderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_messages_chat_members_sender_id"); - - b.Navigation("ChatRoom"); - - b.Navigation("ForwardedMessage"); - - b.Navigation("RepliedMessage"); - - b.Navigation("Sender"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.MessageReaction", b => - { - b.HasOne("DysonNetwork.Sphere.Chat.Message", "Message") - .WithMany("Reactions") - .HasForeignKey("MessageId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_reactions_chat_messages_message_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatMember", "Sender") - .WithMany() - .HasForeignKey("SenderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_reactions_chat_members_sender_id"); - - b.Navigation("Message"); - - b.Navigation("Sender"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.RealtimeCall", b => - { - b.HasOne("DysonNetwork.Sphere.Chat.ChatRoom", "Room") - .WithMany() - .HasForeignKey("RoomId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_realtime_call_chat_rooms_room_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatMember", "Sender") - .WithMany() - .HasForeignKey("SenderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_realtime_call_chat_members_sender_id"); - - b.Navigation("Room"); - - b.Navigation("Sender"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomApp", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Developer") - .WithMany() - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_custom_apps_publishers_publisher_id"); - - b.Navigation("Developer"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomAppSecret", b => - { - b.HasOne("DysonNetwork.Sphere.Developer.CustomApp", "App") - .WithMany() - .HasForeignKey("AppId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_custom_app_secrets_custom_apps_app_id"); - - b.Navigation("App"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroupMember", b => - { - b.HasOne("DysonNetwork.Sphere.Permission.PermissionGroup", "Group") - .WithMany("Members") - .HasForeignKey("GroupId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_permission_group_members_permission_groups_group_id"); - - b.Navigation("Group"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionNode", b => - { - b.HasOne("DysonNetwork.Sphere.Permission.PermissionGroup", "Group") - .WithMany("Nodes") - .HasForeignKey("GroupId") - .HasConstraintName("fk_permission_nodes_permission_groups_group_id"); - - b.Navigation("Group"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.Post", b => - { - b.HasOne("DysonNetwork.Sphere.Post.Post", "ForwardedPost") - .WithMany() - .HasForeignKey("ForwardedPostId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_posts_posts_forwarded_post_id"); - - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Posts") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_posts_publishers_publisher_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", "RepliedPost") - .WithMany() - .HasForeignKey("RepliedPostId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_posts_posts_replied_post_id"); - - b.Navigation("ForwardedPost"); - - b.Navigation("Publisher"); - - b.Navigation("RepliedPost"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostCollection", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Collections") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_collections_publishers_publisher_id"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostReaction", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_reactions_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", "Post") - .WithMany("Reactions") - .HasForeignKey("PostId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_reactions_posts_post_id"); - - b.Navigation("Account"); - - b.Navigation("Post"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.Publisher", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .HasConstraintName("fk_publishers_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Realm.Realm", "Realm") - .WithMany() - .HasForeignKey("RealmId") - .HasConstraintName("fk_publishers_realms_realm_id"); - - b.Navigation("Account"); - - b.Navigation("Realm"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherFeature", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany() - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_features_publishers_publisher_id"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherMember", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_members_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Members") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_members_publishers_publisher_id"); - - b.Navigation("Account"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherSubscription", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_subscriptions_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Subscriptions") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_subscriptions_publishers_publisher_id"); - - b.Navigation("Account"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.Realm", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_realms_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.RealmMember", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_realm_members_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Realm.Realm", "Realm") - .WithMany("Members") - .HasForeignKey("RealmId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_realm_members_realms_realm_id"); - - b.Navigation("Account"); - - b.Navigation("Realm"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.Sticker", b => - { - b.HasOne("DysonNetwork.Sphere.Sticker.StickerPack", "Pack") - .WithMany() - .HasForeignKey("PackId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_stickers_sticker_packs_pack_id"); - - b.Navigation("Pack"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.StickerPack", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany() - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_sticker_packs_publishers_publisher_id"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFile", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_files_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.Message", null) - .WithMany("OutdatedAttachments") - .HasForeignKey("MessageId") - .HasConstraintName("fk_files_chat_messages_message_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany("OutdatedAttachments") - .HasForeignKey("PostId") - .HasConstraintName("fk_files_posts_post_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFileReference", b => - { - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "File") - .WithMany() - .HasForeignKey("FileId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_file_references_files_file_id"); - - b.Navigation("File"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Order", b => - { - b.HasOne("DysonNetwork.Sphere.Developer.CustomApp", "IssuerApp") - .WithMany() - .HasForeignKey("IssuerAppId") - .HasConstraintName("fk_payment_orders_custom_apps_issuer_app_id"); - - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "PayeeWallet") - .WithMany() - .HasForeignKey("PayeeWalletId") - .HasConstraintName("fk_payment_orders_wallets_payee_wallet_id"); - - b.HasOne("DysonNetwork.Sphere.Wallet.Transaction", "Transaction") - .WithMany() - .HasForeignKey("TransactionId") - .HasConstraintName("fk_payment_orders_payment_transactions_transaction_id"); - - b.Navigation("IssuerApp"); - - b.Navigation("PayeeWallet"); - - b.Navigation("Transaction"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Subscription", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Subscriptions") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_wallet_subscriptions_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Wallet.Coupon", "Coupon") - .WithMany() - .HasForeignKey("CouponId") - .HasConstraintName("fk_wallet_subscriptions_wallet_coupons_coupon_id"); - - b.Navigation("Account"); - - b.Navigation("Coupon"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Transaction", b => - { - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "PayeeWallet") - .WithMany() - .HasForeignKey("PayeeWalletId") - .HasConstraintName("fk_payment_transactions_wallets_payee_wallet_id"); - - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "PayerWallet") - .WithMany() - .HasForeignKey("PayerWalletId") - .HasConstraintName("fk_payment_transactions_wallets_payer_wallet_id"); - - b.Navigation("PayeeWallet"); - - b.Navigation("PayerWallet"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Wallet", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_wallets_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.WalletPocket", b => - { - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "Wallet") - .WithMany("Pockets") - .HasForeignKey("WalletId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_wallet_pockets_wallets_wallet_id"); - - b.Navigation("Wallet"); - }); - - modelBuilder.Entity("PostPostCategory", b => - { - b.HasOne("DysonNetwork.Sphere.Post.PostCategory", null) - .WithMany() - .HasForeignKey("CategoriesId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_category_links_post_categories_categories_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany() - .HasForeignKey("PostsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_category_links_posts_posts_id"); - }); - - modelBuilder.Entity("PostPostCollection", b => - { - b.HasOne("DysonNetwork.Sphere.Post.PostCollection", null) - .WithMany() - .HasForeignKey("CollectionsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_collection_links_post_collections_collections_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany() - .HasForeignKey("PostsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_collection_links_posts_posts_id"); - }); - - modelBuilder.Entity("PostPostTag", b => - { - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany() - .HasForeignKey("PostsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_tag_links_posts_posts_id"); - - b.HasOne("DysonNetwork.Sphere.Post.PostTag", null) - .WithMany() - .HasForeignKey("TagsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_tag_links_post_tags_tags_id"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Account", 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"); - - b.Navigation("Subscriptions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatRoom", b => - { - b.Navigation("Members"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.Message", b => - { - b.Navigation("OutdatedAttachments"); - - b.Navigation("Reactions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroup", b => - { - b.Navigation("Members"); - - b.Navigation("Nodes"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.Post", b => - { - b.Navigation("OutdatedAttachments"); - - b.Navigation("Reactions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.Publisher", b => - { - b.Navigation("Collections"); - - b.Navigation("Members"); - - b.Navigation("Posts"); - - b.Navigation("Subscriptions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.Realm", b => - { - b.Navigation("ChatRooms"); - - b.Navigation("Members"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Wallet", b => - { - b.Navigation("Pockets"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/DysonNetwork.Sphere/Migrations/20250625150644_SafetyAbuseReport.cs b/DysonNetwork.Sphere/Migrations/20250625150644_SafetyAbuseReport.cs deleted file mode 100644 index ef0e204..0000000 --- a/DysonNetwork.Sphere/Migrations/20250625150644_SafetyAbuseReport.cs +++ /dev/null @@ -1,66 +0,0 @@ -using System; -using System.Collections.Generic; -using DysonNetwork.Sphere.Storage; -using Microsoft.EntityFrameworkCore.Migrations; -using NodaTime; - -#nullable disable - -namespace DysonNetwork.Sphere.Migrations -{ - /// - public partial class SafetyAbuseReport : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn>( - name: "sensitive_marks", - table: "posts", - type: "jsonb", - nullable: true); - - migrationBuilder.CreateTable( - name: "abuse_reports", - columns: table => new - { - id = table.Column(type: "uuid", nullable: false), - resource_identifier = table.Column(type: "character varying(4096)", maxLength: 4096, nullable: false), - type = table.Column(type: "integer", nullable: false), - reason = table.Column(type: "character varying(8192)", maxLength: 8192, nullable: false), - resolved_at = table.Column(type: "timestamp with time zone", nullable: true), - resolution = table.Column(type: "character varying(8192)", maxLength: 8192, nullable: true), - account_id = table.Column(type: "uuid", nullable: false), - created_at = table.Column(type: "timestamp with time zone", nullable: false), - updated_at = table.Column(type: "timestamp with time zone", nullable: false), - deleted_at = table.Column(type: "timestamp with time zone", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("pk_abuse_reports", x => x.id); - table.ForeignKey( - name: "fk_abuse_reports_accounts_account_id", - column: x => x.account_id, - principalTable: "accounts", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateIndex( - name: "ix_abuse_reports_account_id", - table: "abuse_reports", - column: "account_id"); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "abuse_reports"); - - migrationBuilder.DropColumn( - name: "sensitive_marks", - table: "posts"); - } - } -} diff --git a/DysonNetwork.Sphere/Migrations/20250626093051_AddWebArticles.Designer.cs b/DysonNetwork.Sphere/Migrations/20250626093051_AddWebArticles.Designer.cs deleted file mode 100644 index 99582d5..0000000 --- a/DysonNetwork.Sphere/Migrations/20250626093051_AddWebArticles.Designer.cs +++ /dev/null @@ -1,3852 +0,0 @@ -// -using System; -using System.Collections.Generic; -using System.Text.Json; -using DysonNetwork.Sphere; -using DysonNetwork.Sphere.Account; -using DysonNetwork.Sphere.Chat; -using DysonNetwork.Sphere.Connection.WebReader; -using DysonNetwork.Sphere.Storage; -using DysonNetwork.Sphere.Wallet; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using NetTopologySuite.Geometries; -using NodaTime; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; -using NpgsqlTypes; - -#nullable disable - -namespace DysonNetwork.Sphere.Migrations -{ - [DbContext(typeof(AppDatabase))] - [Migration("20250626093051_AddWebArticles")] - partial class AddWebArticles - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "9.0.3") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "postgis"); - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.AbuseReport", 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.Sphere.Account.Account", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("ActivatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("activated_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("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("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.Sphere.Account.AccountAuthFactor", 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.Sphere.Account.AccountConnection", 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.Sphere.Account.AccountContact", 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("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.Sphere.Account.ActionLog", 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("geometry") - .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.Sphere.Account.Badge", 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.Sphere.Account.CheckInResult", 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("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.Sphere.Account.MagicSpell", 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.Sphere.Account.Notification", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("Content") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .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>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("Priority") - .HasColumnType("integer") - .HasColumnName("priority"); - - b.Property("Subtitle") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("subtitle"); - - b.Property("Title") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("title"); - - b.Property("Topic") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("topic"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("ViewedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("viewed_at"); - - b.HasKey("Id") - .HasName("pk_notifications"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_notifications_account_id"); - - b.ToTable("notifications", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.NotificationPushSubscription", 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(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("device_id"); - - b.Property("DeviceToken") - .IsRequired() - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("device_token"); - - b.Property("LastUsedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_used_at"); - - b.Property("Provider") - .HasColumnType("integer") - .HasColumnName("provider"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_notification_push_subscriptions"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_notification_push_subscriptions_account_id"); - - b.HasIndex("DeviceToken", "DeviceId", "AccountId") - .IsUnique() - .HasDatabaseName("ix_notification_push_subscriptions_device_token_device_id_acco"); - - b.ToTable("notification_push_subscriptions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Profile", 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("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_id"); - - 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("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("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - b.Property("Pronouns") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("pronouns"); - - b.Property("StellarMembership") - .HasColumnType("jsonb") - .HasColumnName("stellar_membership"); - - 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("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.Sphere.Account.Relationship", 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.Sphere.Account.Status", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - 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("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("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.Sphere.Auth.Challenge", 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") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("device_id"); - - 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("geometry") - .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("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_challenges"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_auth_challenges_account_id"); - - b.ToTable("auth_challenges", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Session", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("ChallengeId") - .HasColumnType("uuid") - .HasColumnName("challenge_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("Label") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("label"); - - b.Property("LastGrantedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_granted_at"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - 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.ToTable("auth_sessions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatMember", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("BreakUntil") - .HasColumnType("timestamp with time zone") - .HasColumnName("break_until"); - - b.Property("ChatRoomId") - .HasColumnType("uuid") - .HasColumnName("chat_room_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("IsBot") - .HasColumnType("boolean") - .HasColumnName("is_bot"); - - b.Property("JoinedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("joined_at"); - - b.Property("LastReadAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_read_at"); - - b.Property("LeaveAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("leave_at"); - - b.Property("Nick") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("nick"); - - b.Property("Notify") - .HasColumnType("integer") - .HasColumnName("notify"); - - b.Property("Role") - .HasColumnType("integer") - .HasColumnName("role"); - - b.Property("TimeoutCause") - .HasColumnType("jsonb") - .HasColumnName("timeout_cause"); - - b.Property("TimeoutUntil") - .HasColumnType("timestamp with time zone") - .HasColumnName("timeout_until"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_chat_members"); - - b.HasAlternateKey("ChatRoomId", "AccountId") - .HasName("ak_chat_members_chat_room_id_account_id"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_chat_members_account_id"); - - b.ToTable("chat_members", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatRoom", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("Background") - .HasColumnType("jsonb") - .HasColumnName("background"); - - b.Property("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_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("Description") - .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") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("Picture") - .HasColumnType("jsonb") - .HasColumnName("picture"); - - b.Property("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - b.Property("RealmId") - .HasColumnType("uuid") - .HasColumnName("realm_id"); - - b.Property("Type") - .HasColumnType("integer") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_chat_rooms"); - - b.HasIndex("RealmId") - .HasDatabaseName("ix_chat_rooms_realm_id"); - - b.ToTable("chat_rooms", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.Message", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property>("Attachments") - .IsRequired() - .HasColumnType("jsonb") - .HasColumnName("attachments"); - - b.Property("ChatRoomId") - .HasColumnType("uuid") - .HasColumnName("chat_room_id"); - - b.Property("Content") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .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("EditedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("edited_at"); - - b.Property("ForwardedMessageId") - .HasColumnType("uuid") - .HasColumnName("forwarded_message_id"); - - b.Property>("MembersMentioned") - .HasColumnType("jsonb") - .HasColumnName("members_mentioned"); - - b.Property>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("Nonce") - .IsRequired() - .HasMaxLength(36) - .HasColumnType("character varying(36)") - .HasColumnName("nonce"); - - b.Property("RepliedMessageId") - .HasColumnType("uuid") - .HasColumnName("replied_message_id"); - - b.Property("SenderId") - .HasColumnType("uuid") - .HasColumnName("sender_id"); - - 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_chat_messages"); - - b.HasIndex("ChatRoomId") - .HasDatabaseName("ix_chat_messages_chat_room_id"); - - b.HasIndex("ForwardedMessageId") - .HasDatabaseName("ix_chat_messages_forwarded_message_id"); - - b.HasIndex("RepliedMessageId") - .HasDatabaseName("ix_chat_messages_replied_message_id"); - - b.HasIndex("SenderId") - .HasDatabaseName("ix_chat_messages_sender_id"); - - b.ToTable("chat_messages", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.MessageReaction", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("Attitude") - .HasColumnType("integer") - .HasColumnName("attitude"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("MessageId") - .HasColumnType("uuid") - .HasColumnName("message_id"); - - b.Property("SenderId") - .HasColumnType("uuid") - .HasColumnName("sender_id"); - - b.Property("Symbol") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("symbol"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_chat_reactions"); - - b.HasIndex("MessageId") - .HasDatabaseName("ix_chat_reactions_message_id"); - - b.HasIndex("SenderId") - .HasDatabaseName("ix_chat_reactions_sender_id"); - - b.ToTable("chat_reactions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.RealtimeCall", 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("EndedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("ended_at"); - - b.Property("ProviderName") - .HasColumnType("text") - .HasColumnName("provider_name"); - - b.Property("RoomId") - .HasColumnType("uuid") - .HasColumnName("room_id"); - - b.Property("SenderId") - .HasColumnType("uuid") - .HasColumnName("sender_id"); - - b.Property("SessionId") - .HasColumnType("text") - .HasColumnName("session_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("UpstreamConfigJson") - .HasColumnType("jsonb") - .HasColumnName("upstream"); - - b.HasKey("Id") - .HasName("pk_chat_realtime_call"); - - b.HasIndex("RoomId") - .HasDatabaseName("ix_chat_realtime_call_room_id"); - - b.HasIndex("SenderId") - .HasDatabaseName("ix_chat_realtime_call_sender_id"); - - b.ToTable("chat_realtime_call", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Connection.WebReader.WebArticle", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("Author") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("author"); - - b.Property("Content") - .HasColumnType("text") - .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("FeedId") - .HasColumnType("uuid") - .HasColumnName("feed_id"); - - b.Property>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("Preview") - .HasColumnType("jsonb") - .HasColumnName("preview"); - - b.Property("PublishedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("published_at"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("title"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("Url") - .IsRequired() - .HasMaxLength(8192) - .HasColumnType("character varying(8192)") - .HasColumnName("url"); - - b.HasKey("Id") - .HasName("pk_web_articles"); - - b.HasIndex("FeedId") - .HasDatabaseName("ix_web_articles_feed_id"); - - b.HasIndex("Url") - .IsUnique() - .HasDatabaseName("ix_web_articles_url"); - - b.ToTable("web_articles", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Connection.WebReader.WebFeed", 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("Description") - .HasMaxLength(8192) - .HasColumnType("character varying(8192)") - .HasColumnName("description"); - - b.Property("Preview") - .HasColumnType("jsonb") - .HasColumnName("preview"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("title"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("Url") - .IsRequired() - .HasMaxLength(8192) - .HasColumnType("character varying(8192)") - .HasColumnName("url"); - - b.HasKey("Id") - .HasName("pk_web_feeds"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_web_feeds_publisher_id"); - - b.HasIndex("Url") - .IsUnique() - .HasDatabaseName("ix_web_feeds_url"); - - b.ToTable("web_feeds", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomApp", 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("Name") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("slug"); - - b.Property("Status") - .HasColumnType("integer") - .HasColumnName("status"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("VerifiedAs") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("verified_as"); - - b.Property("VerifiedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("verified_at"); - - b.HasKey("Id") - .HasName("pk_custom_apps"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_custom_apps_publisher_id"); - - b.ToTable("custom_apps", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomAppSecret", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AppId") - .HasColumnType("uuid") - .HasColumnName("app_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("Remarks") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("remarks"); - - b.Property("Secret") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("secret"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_custom_app_secrets"); - - b.HasIndex("AppId") - .HasDatabaseName("ix_custom_app_secrets_app_id"); - - b.ToTable("custom_app_secrets", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroup", 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.Sphere.Permission.PermissionGroupMember", 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.Sphere.Permission.PermissionNode", 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("Area") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("area"); - - 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("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", "Area", "Actor") - .HasDatabaseName("ix_permission_nodes_key_area_actor"); - - b.ToTable("permission_nodes", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.Post", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property>("Attachments") - .IsRequired() - .HasColumnType("jsonb") - .HasColumnName("attachments"); - - b.Property("Content") - .HasColumnType("text") - .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("Description") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("Downvotes") - .HasColumnType("integer") - .HasColumnName("downvotes"); - - b.Property("EditedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("edited_at"); - - b.Property("ForwardedPostId") - .HasColumnType("uuid") - .HasColumnName("forwarded_post_id"); - - b.Property("Language") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("language"); - - b.Property>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("PublishedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("published_at"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("RepliedPostId") - .HasColumnType("uuid") - .HasColumnName("replied_post_id"); - - b.Property("SearchVector") - .IsRequired() - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("tsvector") - .HasColumnName("search_vector") - .HasAnnotation("Npgsql:TsVectorConfig", "simple") - .HasAnnotation("Npgsql:TsVectorProperties", new[] { "Title", "Description", "Content" }); - - b.Property>("SensitiveMarks") - .HasColumnType("jsonb") - .HasColumnName("sensitive_marks"); - - b.Property("Title") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("title"); - - b.Property("Type") - .HasColumnType("integer") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("Upvotes") - .HasColumnType("integer") - .HasColumnName("upvotes"); - - b.Property("ViewsTotal") - .HasColumnType("integer") - .HasColumnName("views_total"); - - b.Property("ViewsUnique") - .HasColumnType("integer") - .HasColumnName("views_unique"); - - b.Property("Visibility") - .HasColumnType("integer") - .HasColumnName("visibility"); - - b.HasKey("Id") - .HasName("pk_posts"); - - b.HasIndex("ForwardedPostId") - .HasDatabaseName("ix_posts_forwarded_post_id"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_posts_publisher_id"); - - b.HasIndex("RepliedPostId") - .HasDatabaseName("ix_posts_replied_post_id"); - - b.HasIndex("SearchVector") - .HasDatabaseName("ix_posts_search_vector"); - - NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex("SearchVector"), "GIN"); - - b.ToTable("posts", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostCategory", 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("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("name"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_categories"); - - b.ToTable("post_categories", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostCollection", 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("Description") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("name"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_collections"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_post_collections_publisher_id"); - - b.ToTable("post_collections", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostReaction", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("Attitude") - .HasColumnType("integer") - .HasColumnName("attitude"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("PostId") - .HasColumnType("uuid") - .HasColumnName("post_id"); - - b.Property("Symbol") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("symbol"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_reactions"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_post_reactions_account_id"); - - b.HasIndex("PostId") - .HasDatabaseName("ix_post_reactions_post_id"); - - b.ToTable("post_reactions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostTag", 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("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("name"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_tags"); - - b.ToTable("post_tags", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.Publisher", 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("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_id"); - - b.Property("Bio") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("bio"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - 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("Picture") - .HasColumnType("jsonb") - .HasColumnName("picture"); - - b.Property("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - b.Property("RealmId") - .HasColumnType("uuid") - .HasColumnName("realm_id"); - - b.Property("Type") - .HasColumnType("integer") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("Verification") - .HasColumnType("jsonb") - .HasColumnName("verification"); - - b.HasKey("Id") - .HasName("pk_publishers"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_publishers_account_id"); - - b.HasIndex("Name") - .IsUnique() - .HasDatabaseName("ix_publishers_name"); - - b.HasIndex("RealmId") - .HasDatabaseName("ix_publishers_realm_id"); - - b.ToTable("publishers", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherFeature", 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("ExpiredAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expired_at"); - - b.Property("Flag") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("flag"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_publisher_features"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_publisher_features_publisher_id"); - - b.ToTable("publisher_features", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherMember", b => - { - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_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("Role") - .HasColumnType("integer") - .HasColumnName("role"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("PublisherId", "AccountId") - .HasName("pk_publisher_members"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_publisher_members_account_id"); - - b.ToTable("publisher_members", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherSubscription", 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("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("Status") - .HasColumnType("integer") - .HasColumnName("status"); - - b.Property("Tier") - .HasColumnType("integer") - .HasColumnName("tier"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_publisher_subscriptions"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_publisher_subscriptions_account_id"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_publisher_subscriptions_publisher_id"); - - b.ToTable("publisher_subscriptions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.Realm", 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("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_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("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("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - 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("AccountId") - .HasDatabaseName("ix_realms_account_id"); - - b.HasIndex("Slug") - .IsUnique() - .HasDatabaseName("ix_realms_slug"); - - b.ToTable("realms", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.RealmMember", 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.HasIndex("AccountId") - .HasDatabaseName("ix_realm_members_account_id"); - - b.ToTable("realm_members", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.Sticker", 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("Image") - .HasColumnType("jsonb") - .HasColumnName("image"); - - b.Property("ImageId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("image_id"); - - b.Property("PackId") - .HasColumnType("uuid") - .HasColumnName("pack_id"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_stickers"); - - b.HasIndex("PackId") - .HasDatabaseName("ix_stickers_pack_id"); - - b.HasIndex("Slug") - .HasDatabaseName("ix_stickers_slug"); - - b.ToTable("stickers", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.StickerPack", 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("Description") - .IsRequired() - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("Prefix") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("prefix"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_sticker_packs"); - - b.HasIndex("Prefix") - .IsUnique() - .HasDatabaseName("ix_sticker_packs_prefix"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_sticker_packs_publisher_id"); - - b.ToTable("sticker_packs", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFile", b => - { - b.Property("Id") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .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("Description") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property>("FileMeta") - .HasColumnType("jsonb") - .HasColumnName("file_meta"); - - b.Property("HasCompression") - .HasColumnType("boolean") - .HasColumnName("has_compression"); - - b.Property("Hash") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("hash"); - - b.Property("IsMarkedRecycle") - .HasColumnType("boolean") - .HasColumnName("is_marked_recycle"); - - b.Property("MessageId") - .HasColumnType("uuid") - .HasColumnName("message_id"); - - b.Property("MimeType") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("mime_type"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("PostId") - .HasColumnType("uuid") - .HasColumnName("post_id"); - - b.Property>("SensitiveMarks") - .HasColumnType("jsonb") - .HasColumnName("sensitive_marks"); - - b.Property("Size") - .HasColumnType("bigint") - .HasColumnName("size"); - - b.Property("StorageId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("storage_id"); - - b.Property("StorageUrl") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("storage_url"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("UploadedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("uploaded_at"); - - b.Property("UploadedTo") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("uploaded_to"); - - b.Property>("UserMeta") - .HasColumnType("jsonb") - .HasColumnName("user_meta"); - - b.HasKey("Id") - .HasName("pk_files"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_files_account_id"); - - b.HasIndex("MessageId") - .HasDatabaseName("ix_files_message_id"); - - b.HasIndex("PostId") - .HasDatabaseName("ix_files_post_id"); - - b.ToTable("files", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFileReference", 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("ExpiredAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expired_at"); - - b.Property("FileId") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("file_id"); - - b.Property("ResourceId") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("resource_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("Usage") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("usage"); - - b.HasKey("Id") - .HasName("pk_file_references"); - - b.HasIndex("FileId") - .HasDatabaseName("ix_file_references_file_id"); - - b.ToTable("file_references", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Coupon", 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.Sphere.Wallet.Order", 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("IssuerAppId") - .HasColumnType("uuid") - .HasColumnName("issuer_app_id"); - - b.Property>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("PayeeWalletId") - .HasColumnType("uuid") - .HasColumnName("payee_wallet_id"); - - 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("IssuerAppId") - .HasDatabaseName("ix_payment_orders_issuer_app_id"); - - 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.Sphere.Wallet.Subscription", 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.ToTable("wallet_subscriptions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Transaction", 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.Sphere.Wallet.Wallet", 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.Sphere.Wallet.WalletPocket", 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("PostPostCategory", b => - { - b.Property("CategoriesId") - .HasColumnType("uuid") - .HasColumnName("categories_id"); - - b.Property("PostsId") - .HasColumnType("uuid") - .HasColumnName("posts_id"); - - b.HasKey("CategoriesId", "PostsId") - .HasName("pk_post_category_links"); - - b.HasIndex("PostsId") - .HasDatabaseName("ix_post_category_links_posts_id"); - - b.ToTable("post_category_links", (string)null); - }); - - modelBuilder.Entity("PostPostCollection", b => - { - b.Property("CollectionsId") - .HasColumnType("uuid") - .HasColumnName("collections_id"); - - b.Property("PostsId") - .HasColumnType("uuid") - .HasColumnName("posts_id"); - - b.HasKey("CollectionsId", "PostsId") - .HasName("pk_post_collection_links"); - - b.HasIndex("PostsId") - .HasDatabaseName("ix_post_collection_links_posts_id"); - - b.ToTable("post_collection_links", (string)null); - }); - - modelBuilder.Entity("PostPostTag", b => - { - b.Property("PostsId") - .HasColumnType("uuid") - .HasColumnName("posts_id"); - - b.Property("TagsId") - .HasColumnType("uuid") - .HasColumnName("tags_id"); - - b.HasKey("PostsId", "TagsId") - .HasName("pk_post_tag_links"); - - b.HasIndex("TagsId") - .HasDatabaseName("ix_post_tag_links_tags_id"); - - b.ToTable("post_tag_links", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.AbuseReport", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_abuse_reports_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.AccountAuthFactor", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("AuthFactors") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_auth_factors_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.AccountConnection", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Connections") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_connections_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.AccountContact", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Contacts") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_contacts_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.ActionLog", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_action_logs_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Badge", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Badges") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_badges_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.CheckInResult", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_check_in_results_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.MagicSpell", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .HasConstraintName("fk_magic_spells_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Notification", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_notifications_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.NotificationPushSubscription", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_notification_push_subscriptions_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Profile", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithOne("Profile") - .HasForeignKey("DysonNetwork.Sphere.Account.Profile", "AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_profiles_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Relationship", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("OutgoingRelationships") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_relationships_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Account.Account", "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.Sphere.Account.Status", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_statuses_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Challenge", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Challenges") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_challenges_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Session", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Sessions") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_sessions_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Auth.Challenge", "Challenge") - .WithMany() - .HasForeignKey("ChallengeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_sessions_auth_challenges_challenge_id"); - - b.Navigation("Account"); - - b.Navigation("Challenge"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatMember", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_members_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatRoom", "ChatRoom") - .WithMany("Members") - .HasForeignKey("ChatRoomId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_members_chat_rooms_chat_room_id"); - - b.Navigation("Account"); - - b.Navigation("ChatRoom"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatRoom", b => - { - b.HasOne("DysonNetwork.Sphere.Realm.Realm", "Realm") - .WithMany("ChatRooms") - .HasForeignKey("RealmId") - .HasConstraintName("fk_chat_rooms_realms_realm_id"); - - b.Navigation("Realm"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.Message", b => - { - b.HasOne("DysonNetwork.Sphere.Chat.ChatRoom", "ChatRoom") - .WithMany() - .HasForeignKey("ChatRoomId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_messages_chat_rooms_chat_room_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.Message", "ForwardedMessage") - .WithMany() - .HasForeignKey("ForwardedMessageId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_chat_messages_chat_messages_forwarded_message_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.Message", "RepliedMessage") - .WithMany() - .HasForeignKey("RepliedMessageId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_chat_messages_chat_messages_replied_message_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatMember", "Sender") - .WithMany() - .HasForeignKey("SenderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_messages_chat_members_sender_id"); - - b.Navigation("ChatRoom"); - - b.Navigation("ForwardedMessage"); - - b.Navigation("RepliedMessage"); - - b.Navigation("Sender"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.MessageReaction", b => - { - b.HasOne("DysonNetwork.Sphere.Chat.Message", "Message") - .WithMany("Reactions") - .HasForeignKey("MessageId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_reactions_chat_messages_message_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatMember", "Sender") - .WithMany() - .HasForeignKey("SenderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_reactions_chat_members_sender_id"); - - b.Navigation("Message"); - - b.Navigation("Sender"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.RealtimeCall", b => - { - b.HasOne("DysonNetwork.Sphere.Chat.ChatRoom", "Room") - .WithMany() - .HasForeignKey("RoomId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_realtime_call_chat_rooms_room_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatMember", "Sender") - .WithMany() - .HasForeignKey("SenderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_realtime_call_chat_members_sender_id"); - - b.Navigation("Room"); - - b.Navigation("Sender"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Connection.WebReader.WebArticle", b => - { - b.HasOne("DysonNetwork.Sphere.Connection.WebReader.WebFeed", "Feed") - .WithMany("Articles") - .HasForeignKey("FeedId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_web_articles_web_feeds_feed_id"); - - b.Navigation("Feed"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Connection.WebReader.WebFeed", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany() - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_web_feeds_publishers_publisher_id"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomApp", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Developer") - .WithMany() - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_custom_apps_publishers_publisher_id"); - - b.Navigation("Developer"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomAppSecret", b => - { - b.HasOne("DysonNetwork.Sphere.Developer.CustomApp", "App") - .WithMany() - .HasForeignKey("AppId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_custom_app_secrets_custom_apps_app_id"); - - b.Navigation("App"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroupMember", b => - { - b.HasOne("DysonNetwork.Sphere.Permission.PermissionGroup", "Group") - .WithMany("Members") - .HasForeignKey("GroupId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_permission_group_members_permission_groups_group_id"); - - b.Navigation("Group"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionNode", b => - { - b.HasOne("DysonNetwork.Sphere.Permission.PermissionGroup", "Group") - .WithMany("Nodes") - .HasForeignKey("GroupId") - .HasConstraintName("fk_permission_nodes_permission_groups_group_id"); - - b.Navigation("Group"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.Post", b => - { - b.HasOne("DysonNetwork.Sphere.Post.Post", "ForwardedPost") - .WithMany() - .HasForeignKey("ForwardedPostId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_posts_posts_forwarded_post_id"); - - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Posts") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_posts_publishers_publisher_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", "RepliedPost") - .WithMany() - .HasForeignKey("RepliedPostId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_posts_posts_replied_post_id"); - - b.Navigation("ForwardedPost"); - - b.Navigation("Publisher"); - - b.Navigation("RepliedPost"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostCollection", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Collections") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_collections_publishers_publisher_id"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostReaction", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_reactions_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", "Post") - .WithMany("Reactions") - .HasForeignKey("PostId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_reactions_posts_post_id"); - - b.Navigation("Account"); - - b.Navigation("Post"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.Publisher", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .HasConstraintName("fk_publishers_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Realm.Realm", "Realm") - .WithMany() - .HasForeignKey("RealmId") - .HasConstraintName("fk_publishers_realms_realm_id"); - - b.Navigation("Account"); - - b.Navigation("Realm"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherFeature", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany() - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_features_publishers_publisher_id"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherMember", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_members_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Members") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_members_publishers_publisher_id"); - - b.Navigation("Account"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherSubscription", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_subscriptions_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Subscriptions") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_subscriptions_publishers_publisher_id"); - - b.Navigation("Account"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.Realm", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_realms_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.RealmMember", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_realm_members_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Realm.Realm", "Realm") - .WithMany("Members") - .HasForeignKey("RealmId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_realm_members_realms_realm_id"); - - b.Navigation("Account"); - - b.Navigation("Realm"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.Sticker", b => - { - b.HasOne("DysonNetwork.Sphere.Sticker.StickerPack", "Pack") - .WithMany() - .HasForeignKey("PackId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_stickers_sticker_packs_pack_id"); - - b.Navigation("Pack"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.StickerPack", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany() - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_sticker_packs_publishers_publisher_id"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFile", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_files_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.Message", null) - .WithMany("OutdatedAttachments") - .HasForeignKey("MessageId") - .HasConstraintName("fk_files_chat_messages_message_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany("OutdatedAttachments") - .HasForeignKey("PostId") - .HasConstraintName("fk_files_posts_post_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFileReference", b => - { - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "File") - .WithMany() - .HasForeignKey("FileId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_file_references_files_file_id"); - - b.Navigation("File"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Order", b => - { - b.HasOne("DysonNetwork.Sphere.Developer.CustomApp", "IssuerApp") - .WithMany() - .HasForeignKey("IssuerAppId") - .HasConstraintName("fk_payment_orders_custom_apps_issuer_app_id"); - - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "PayeeWallet") - .WithMany() - .HasForeignKey("PayeeWalletId") - .HasConstraintName("fk_payment_orders_wallets_payee_wallet_id"); - - b.HasOne("DysonNetwork.Sphere.Wallet.Transaction", "Transaction") - .WithMany() - .HasForeignKey("TransactionId") - .HasConstraintName("fk_payment_orders_payment_transactions_transaction_id"); - - b.Navigation("IssuerApp"); - - b.Navigation("PayeeWallet"); - - b.Navigation("Transaction"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Subscription", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Subscriptions") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_wallet_subscriptions_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Wallet.Coupon", "Coupon") - .WithMany() - .HasForeignKey("CouponId") - .HasConstraintName("fk_wallet_subscriptions_wallet_coupons_coupon_id"); - - b.Navigation("Account"); - - b.Navigation("Coupon"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Transaction", b => - { - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "PayeeWallet") - .WithMany() - .HasForeignKey("PayeeWalletId") - .HasConstraintName("fk_payment_transactions_wallets_payee_wallet_id"); - - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "PayerWallet") - .WithMany() - .HasForeignKey("PayerWalletId") - .HasConstraintName("fk_payment_transactions_wallets_payer_wallet_id"); - - b.Navigation("PayeeWallet"); - - b.Navigation("PayerWallet"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Wallet", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_wallets_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.WalletPocket", b => - { - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "Wallet") - .WithMany("Pockets") - .HasForeignKey("WalletId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_wallet_pockets_wallets_wallet_id"); - - b.Navigation("Wallet"); - }); - - modelBuilder.Entity("PostPostCategory", b => - { - b.HasOne("DysonNetwork.Sphere.Post.PostCategory", null) - .WithMany() - .HasForeignKey("CategoriesId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_category_links_post_categories_categories_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany() - .HasForeignKey("PostsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_category_links_posts_posts_id"); - }); - - modelBuilder.Entity("PostPostCollection", b => - { - b.HasOne("DysonNetwork.Sphere.Post.PostCollection", null) - .WithMany() - .HasForeignKey("CollectionsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_collection_links_post_collections_collections_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany() - .HasForeignKey("PostsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_collection_links_posts_posts_id"); - }); - - modelBuilder.Entity("PostPostTag", b => - { - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany() - .HasForeignKey("PostsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_tag_links_posts_posts_id"); - - b.HasOne("DysonNetwork.Sphere.Post.PostTag", null) - .WithMany() - .HasForeignKey("TagsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_tag_links_post_tags_tags_id"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Account", 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"); - - b.Navigation("Subscriptions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatRoom", b => - { - b.Navigation("Members"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.Message", b => - { - b.Navigation("OutdatedAttachments"); - - b.Navigation("Reactions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Connection.WebReader.WebFeed", b => - { - b.Navigation("Articles"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroup", b => - { - b.Navigation("Members"); - - b.Navigation("Nodes"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.Post", b => - { - b.Navigation("OutdatedAttachments"); - - b.Navigation("Reactions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.Publisher", b => - { - b.Navigation("Collections"); - - b.Navigation("Members"); - - b.Navigation("Posts"); - - b.Navigation("Subscriptions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.Realm", b => - { - b.Navigation("ChatRooms"); - - b.Navigation("Members"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Wallet", b => - { - b.Navigation("Pockets"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/DysonNetwork.Sphere/Migrations/20250626093051_AddWebArticles.cs b/DysonNetwork.Sphere/Migrations/20250626093051_AddWebArticles.cs deleted file mode 100644 index 842e213..0000000 --- a/DysonNetwork.Sphere/Migrations/20250626093051_AddWebArticles.cs +++ /dev/null @@ -1,103 +0,0 @@ -using System; -using System.Collections.Generic; -using DysonNetwork.Sphere.Connection.WebReader; -using Microsoft.EntityFrameworkCore.Migrations; -using NodaTime; - -#nullable disable - -namespace DysonNetwork.Sphere.Migrations -{ - /// - public partial class AddWebArticles : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "web_feeds", - columns: table => new - { - id = table.Column(type: "uuid", nullable: false), - url = table.Column(type: "character varying(8192)", maxLength: 8192, nullable: false), - title = table.Column(type: "character varying(4096)", maxLength: 4096, nullable: false), - description = table.Column(type: "character varying(8192)", maxLength: 8192, nullable: true), - preview = table.Column(type: "jsonb", nullable: true), - publisher_id = table.Column(type: "uuid", nullable: false), - created_at = table.Column(type: "timestamp with time zone", nullable: false), - updated_at = table.Column(type: "timestamp with time zone", nullable: false), - deleted_at = table.Column(type: "timestamp with time zone", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("pk_web_feeds", x => x.id); - table.ForeignKey( - name: "fk_web_feeds_publishers_publisher_id", - column: x => x.publisher_id, - principalTable: "publishers", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "web_articles", - columns: table => new - { - id = table.Column(type: "uuid", nullable: false), - title = table.Column(type: "character varying(4096)", maxLength: 4096, nullable: false), - url = table.Column(type: "character varying(8192)", maxLength: 8192, nullable: false), - author = table.Column(type: "character varying(4096)", maxLength: 4096, nullable: true), - meta = table.Column>(type: "jsonb", nullable: true), - preview = table.Column(type: "jsonb", nullable: true), - content = table.Column(type: "text", nullable: true), - published_at = table.Column(type: "timestamp with time zone", nullable: true), - feed_id = table.Column(type: "uuid", nullable: false), - created_at = table.Column(type: "timestamp with time zone", nullable: false), - updated_at = table.Column(type: "timestamp with time zone", nullable: false), - deleted_at = table.Column(type: "timestamp with time zone", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("pk_web_articles", x => x.id); - table.ForeignKey( - name: "fk_web_articles_web_feeds_feed_id", - column: x => x.feed_id, - principalTable: "web_feeds", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateIndex( - name: "ix_web_articles_feed_id", - table: "web_articles", - column: "feed_id"); - - migrationBuilder.CreateIndex( - name: "ix_web_articles_url", - table: "web_articles", - column: "url", - unique: true); - - migrationBuilder.CreateIndex( - name: "ix_web_feeds_publisher_id", - table: "web_feeds", - column: "publisher_id"); - - migrationBuilder.CreateIndex( - name: "ix_web_feeds_url", - table: "web_feeds", - column: "url", - unique: true); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "web_articles"); - - migrationBuilder.DropTable( - name: "web_feeds"); - } - } -} diff --git a/DysonNetwork.Sphere/Migrations/20250626105203_AddRealmTags.Designer.cs b/DysonNetwork.Sphere/Migrations/20250626105203_AddRealmTags.Designer.cs deleted file mode 100644 index 308d6ba..0000000 --- a/DysonNetwork.Sphere/Migrations/20250626105203_AddRealmTags.Designer.cs +++ /dev/null @@ -1,3947 +0,0 @@ -// -using System; -using System.Collections.Generic; -using System.Text.Json; -using DysonNetwork.Sphere; -using DysonNetwork.Sphere.Account; -using DysonNetwork.Sphere.Chat; -using DysonNetwork.Sphere.Connection.WebReader; -using DysonNetwork.Sphere.Storage; -using DysonNetwork.Sphere.Wallet; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using NetTopologySuite.Geometries; -using NodaTime; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; -using NpgsqlTypes; - -#nullable disable - -namespace DysonNetwork.Sphere.Migrations -{ - [DbContext(typeof(AppDatabase))] - [Migration("20250626105203_AddRealmTags")] - partial class AddRealmTags - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "9.0.3") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "postgis"); - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.AbuseReport", 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.Sphere.Account.Account", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("ActivatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("activated_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("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("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.Sphere.Account.AccountAuthFactor", 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.Sphere.Account.AccountConnection", 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.Sphere.Account.AccountContact", 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("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.Sphere.Account.ActionLog", 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("geometry") - .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.Sphere.Account.Badge", 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.Sphere.Account.CheckInResult", 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("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.Sphere.Account.MagicSpell", 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.Sphere.Account.Notification", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("Content") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .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>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("Priority") - .HasColumnType("integer") - .HasColumnName("priority"); - - b.Property("Subtitle") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("subtitle"); - - b.Property("Title") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("title"); - - b.Property("Topic") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("topic"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("ViewedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("viewed_at"); - - b.HasKey("Id") - .HasName("pk_notifications"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_notifications_account_id"); - - b.ToTable("notifications", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.NotificationPushSubscription", 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(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("device_id"); - - b.Property("DeviceToken") - .IsRequired() - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("device_token"); - - b.Property("LastUsedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_used_at"); - - b.Property("Provider") - .HasColumnType("integer") - .HasColumnName("provider"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_notification_push_subscriptions"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_notification_push_subscriptions_account_id"); - - b.HasIndex("DeviceToken", "DeviceId", "AccountId") - .IsUnique() - .HasDatabaseName("ix_notification_push_subscriptions_device_token_device_id_acco"); - - b.ToTable("notification_push_subscriptions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Profile", 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("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_id"); - - 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("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("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - b.Property("Pronouns") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("pronouns"); - - b.Property("StellarMembership") - .HasColumnType("jsonb") - .HasColumnName("stellar_membership"); - - 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("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.Sphere.Account.Relationship", 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.Sphere.Account.Status", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - 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("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("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.Sphere.Auth.Challenge", 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") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("device_id"); - - 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("geometry") - .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("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_challenges"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_auth_challenges_account_id"); - - b.ToTable("auth_challenges", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Session", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("ChallengeId") - .HasColumnType("uuid") - .HasColumnName("challenge_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("Label") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("label"); - - b.Property("LastGrantedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_granted_at"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - 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.ToTable("auth_sessions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatMember", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("BreakUntil") - .HasColumnType("timestamp with time zone") - .HasColumnName("break_until"); - - b.Property("ChatRoomId") - .HasColumnType("uuid") - .HasColumnName("chat_room_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("IsBot") - .HasColumnType("boolean") - .HasColumnName("is_bot"); - - b.Property("JoinedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("joined_at"); - - b.Property("LastReadAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_read_at"); - - b.Property("LeaveAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("leave_at"); - - b.Property("Nick") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("nick"); - - b.Property("Notify") - .HasColumnType("integer") - .HasColumnName("notify"); - - b.Property("Role") - .HasColumnType("integer") - .HasColumnName("role"); - - b.Property("TimeoutCause") - .HasColumnType("jsonb") - .HasColumnName("timeout_cause"); - - b.Property("TimeoutUntil") - .HasColumnType("timestamp with time zone") - .HasColumnName("timeout_until"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_chat_members"); - - b.HasAlternateKey("ChatRoomId", "AccountId") - .HasName("ak_chat_members_chat_room_id_account_id"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_chat_members_account_id"); - - b.ToTable("chat_members", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatRoom", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("Background") - .HasColumnType("jsonb") - .HasColumnName("background"); - - b.Property("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_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("Description") - .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") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("Picture") - .HasColumnType("jsonb") - .HasColumnName("picture"); - - b.Property("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - b.Property("RealmId") - .HasColumnType("uuid") - .HasColumnName("realm_id"); - - b.Property("Type") - .HasColumnType("integer") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_chat_rooms"); - - b.HasIndex("RealmId") - .HasDatabaseName("ix_chat_rooms_realm_id"); - - b.ToTable("chat_rooms", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.Message", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property>("Attachments") - .IsRequired() - .HasColumnType("jsonb") - .HasColumnName("attachments"); - - b.Property("ChatRoomId") - .HasColumnType("uuid") - .HasColumnName("chat_room_id"); - - b.Property("Content") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .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("EditedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("edited_at"); - - b.Property("ForwardedMessageId") - .HasColumnType("uuid") - .HasColumnName("forwarded_message_id"); - - b.Property>("MembersMentioned") - .HasColumnType("jsonb") - .HasColumnName("members_mentioned"); - - b.Property>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("Nonce") - .IsRequired() - .HasMaxLength(36) - .HasColumnType("character varying(36)") - .HasColumnName("nonce"); - - b.Property("RepliedMessageId") - .HasColumnType("uuid") - .HasColumnName("replied_message_id"); - - b.Property("SenderId") - .HasColumnType("uuid") - .HasColumnName("sender_id"); - - 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_chat_messages"); - - b.HasIndex("ChatRoomId") - .HasDatabaseName("ix_chat_messages_chat_room_id"); - - b.HasIndex("ForwardedMessageId") - .HasDatabaseName("ix_chat_messages_forwarded_message_id"); - - b.HasIndex("RepliedMessageId") - .HasDatabaseName("ix_chat_messages_replied_message_id"); - - b.HasIndex("SenderId") - .HasDatabaseName("ix_chat_messages_sender_id"); - - b.ToTable("chat_messages", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.MessageReaction", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("Attitude") - .HasColumnType("integer") - .HasColumnName("attitude"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("MessageId") - .HasColumnType("uuid") - .HasColumnName("message_id"); - - b.Property("SenderId") - .HasColumnType("uuid") - .HasColumnName("sender_id"); - - b.Property("Symbol") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("symbol"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_chat_reactions"); - - b.HasIndex("MessageId") - .HasDatabaseName("ix_chat_reactions_message_id"); - - b.HasIndex("SenderId") - .HasDatabaseName("ix_chat_reactions_sender_id"); - - b.ToTable("chat_reactions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.RealtimeCall", 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("EndedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("ended_at"); - - b.Property("ProviderName") - .HasColumnType("text") - .HasColumnName("provider_name"); - - b.Property("RoomId") - .HasColumnType("uuid") - .HasColumnName("room_id"); - - b.Property("SenderId") - .HasColumnType("uuid") - .HasColumnName("sender_id"); - - b.Property("SessionId") - .HasColumnType("text") - .HasColumnName("session_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("UpstreamConfigJson") - .HasColumnType("jsonb") - .HasColumnName("upstream"); - - b.HasKey("Id") - .HasName("pk_chat_realtime_call"); - - b.HasIndex("RoomId") - .HasDatabaseName("ix_chat_realtime_call_room_id"); - - b.HasIndex("SenderId") - .HasDatabaseName("ix_chat_realtime_call_sender_id"); - - b.ToTable("chat_realtime_call", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Connection.WebReader.WebArticle", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("Author") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("author"); - - b.Property("Content") - .HasColumnType("text") - .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("FeedId") - .HasColumnType("uuid") - .HasColumnName("feed_id"); - - b.Property>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("Preview") - .HasColumnType("jsonb") - .HasColumnName("preview"); - - b.Property("PublishedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("published_at"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("title"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("Url") - .IsRequired() - .HasMaxLength(8192) - .HasColumnType("character varying(8192)") - .HasColumnName("url"); - - b.HasKey("Id") - .HasName("pk_web_articles"); - - b.HasIndex("FeedId") - .HasDatabaseName("ix_web_articles_feed_id"); - - b.HasIndex("Url") - .IsUnique() - .HasDatabaseName("ix_web_articles_url"); - - b.ToTable("web_articles", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Connection.WebReader.WebFeed", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("Config") - .IsRequired() - .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("Description") - .HasMaxLength(8192) - .HasColumnType("character varying(8192)") - .HasColumnName("description"); - - b.Property("Preview") - .HasColumnType("jsonb") - .HasColumnName("preview"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("title"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("Url") - .IsRequired() - .HasMaxLength(8192) - .HasColumnType("character varying(8192)") - .HasColumnName("url"); - - b.HasKey("Id") - .HasName("pk_web_feeds"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_web_feeds_publisher_id"); - - b.HasIndex("Url") - .IsUnique() - .HasDatabaseName("ix_web_feeds_url"); - - b.ToTable("web_feeds", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomApp", 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("Name") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("slug"); - - b.Property("Status") - .HasColumnType("integer") - .HasColumnName("status"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("VerifiedAs") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("verified_as"); - - b.Property("VerifiedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("verified_at"); - - b.HasKey("Id") - .HasName("pk_custom_apps"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_custom_apps_publisher_id"); - - b.ToTable("custom_apps", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomAppSecret", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AppId") - .HasColumnType("uuid") - .HasColumnName("app_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("Remarks") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("remarks"); - - b.Property("Secret") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("secret"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_custom_app_secrets"); - - b.HasIndex("AppId") - .HasDatabaseName("ix_custom_app_secrets_app_id"); - - b.ToTable("custom_app_secrets", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroup", 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.Sphere.Permission.PermissionGroupMember", 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.Sphere.Permission.PermissionNode", 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("Area") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("area"); - - 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("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", "Area", "Actor") - .HasDatabaseName("ix_permission_nodes_key_area_actor"); - - b.ToTable("permission_nodes", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.Post", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property>("Attachments") - .IsRequired() - .HasColumnType("jsonb") - .HasColumnName("attachments"); - - b.Property("Content") - .HasColumnType("text") - .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("Description") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("Downvotes") - .HasColumnType("integer") - .HasColumnName("downvotes"); - - b.Property("EditedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("edited_at"); - - b.Property("ForwardedPostId") - .HasColumnType("uuid") - .HasColumnName("forwarded_post_id"); - - b.Property("Language") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("language"); - - b.Property>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("PublishedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("published_at"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("RepliedPostId") - .HasColumnType("uuid") - .HasColumnName("replied_post_id"); - - b.Property("SearchVector") - .IsRequired() - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("tsvector") - .HasColumnName("search_vector") - .HasAnnotation("Npgsql:TsVectorConfig", "simple") - .HasAnnotation("Npgsql:TsVectorProperties", new[] { "Title", "Description", "Content" }); - - b.Property>("SensitiveMarks") - .HasColumnType("jsonb") - .HasColumnName("sensitive_marks"); - - b.Property("Title") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("title"); - - b.Property("Type") - .HasColumnType("integer") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("Upvotes") - .HasColumnType("integer") - .HasColumnName("upvotes"); - - b.Property("ViewsTotal") - .HasColumnType("integer") - .HasColumnName("views_total"); - - b.Property("ViewsUnique") - .HasColumnType("integer") - .HasColumnName("views_unique"); - - b.Property("Visibility") - .HasColumnType("integer") - .HasColumnName("visibility"); - - b.HasKey("Id") - .HasName("pk_posts"); - - b.HasIndex("ForwardedPostId") - .HasDatabaseName("ix_posts_forwarded_post_id"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_posts_publisher_id"); - - b.HasIndex("RepliedPostId") - .HasDatabaseName("ix_posts_replied_post_id"); - - b.HasIndex("SearchVector") - .HasDatabaseName("ix_posts_search_vector"); - - NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex("SearchVector"), "GIN"); - - b.ToTable("posts", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostCategory", 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("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("name"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_categories"); - - b.ToTable("post_categories", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostCollection", 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("Description") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("name"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_collections"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_post_collections_publisher_id"); - - b.ToTable("post_collections", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostReaction", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("Attitude") - .HasColumnType("integer") - .HasColumnName("attitude"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("PostId") - .HasColumnType("uuid") - .HasColumnName("post_id"); - - b.Property("Symbol") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("symbol"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_reactions"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_post_reactions_account_id"); - - b.HasIndex("PostId") - .HasDatabaseName("ix_post_reactions_post_id"); - - b.ToTable("post_reactions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostTag", 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("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("name"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_tags"); - - b.ToTable("post_tags", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.Publisher", 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("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_id"); - - b.Property("Bio") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("bio"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - 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("Picture") - .HasColumnType("jsonb") - .HasColumnName("picture"); - - b.Property("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - b.Property("RealmId") - .HasColumnType("uuid") - .HasColumnName("realm_id"); - - b.Property("Type") - .HasColumnType("integer") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("Verification") - .HasColumnType("jsonb") - .HasColumnName("verification"); - - b.HasKey("Id") - .HasName("pk_publishers"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_publishers_account_id"); - - b.HasIndex("Name") - .IsUnique() - .HasDatabaseName("ix_publishers_name"); - - b.HasIndex("RealmId") - .HasDatabaseName("ix_publishers_realm_id"); - - b.ToTable("publishers", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherFeature", 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("ExpiredAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expired_at"); - - b.Property("Flag") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("flag"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_publisher_features"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_publisher_features_publisher_id"); - - b.ToTable("publisher_features", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherMember", b => - { - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_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("Role") - .HasColumnType("integer") - .HasColumnName("role"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("PublisherId", "AccountId") - .HasName("pk_publisher_members"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_publisher_members_account_id"); - - b.ToTable("publisher_members", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherSubscription", 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("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("Status") - .HasColumnType("integer") - .HasColumnName("status"); - - b.Property("Tier") - .HasColumnType("integer") - .HasColumnName("tier"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_publisher_subscriptions"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_publisher_subscriptions_account_id"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_publisher_subscriptions_publisher_id"); - - b.ToTable("publisher_subscriptions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.Realm", 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("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_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("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("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - 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("AccountId") - .HasDatabaseName("ix_realms_account_id"); - - b.HasIndex("Slug") - .IsUnique() - .HasDatabaseName("ix_realms_slug"); - - b.ToTable("realms", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.RealmMember", 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.HasIndex("AccountId") - .HasDatabaseName("ix_realm_members_account_id"); - - b.ToTable("realm_members", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.RealmTag", b => - { - b.Property("RealmId") - .HasColumnType("uuid") - .HasColumnName("realm_id"); - - b.Property("TagId") - .HasColumnType("uuid") - .HasColumnName("tag_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("RealmId", "TagId") - .HasName("pk_realm_tags"); - - b.HasIndex("TagId") - .HasDatabaseName("ix_realm_tags_tag_id"); - - b.ToTable("realm_tags", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.Tag", 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("Name") - .IsRequired() - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("name"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_tags"); - - b.ToTable("tags", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.Sticker", 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("Image") - .HasColumnType("jsonb") - .HasColumnName("image"); - - b.Property("ImageId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("image_id"); - - b.Property("PackId") - .HasColumnType("uuid") - .HasColumnName("pack_id"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_stickers"); - - b.HasIndex("PackId") - .HasDatabaseName("ix_stickers_pack_id"); - - b.HasIndex("Slug") - .HasDatabaseName("ix_stickers_slug"); - - b.ToTable("stickers", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.StickerPack", 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("Description") - .IsRequired() - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("Prefix") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("prefix"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_sticker_packs"); - - b.HasIndex("Prefix") - .IsUnique() - .HasDatabaseName("ix_sticker_packs_prefix"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_sticker_packs_publisher_id"); - - b.ToTable("sticker_packs", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFile", b => - { - b.Property("Id") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .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("Description") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property>("FileMeta") - .HasColumnType("jsonb") - .HasColumnName("file_meta"); - - b.Property("HasCompression") - .HasColumnType("boolean") - .HasColumnName("has_compression"); - - b.Property("Hash") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("hash"); - - b.Property("IsMarkedRecycle") - .HasColumnType("boolean") - .HasColumnName("is_marked_recycle"); - - b.Property("MessageId") - .HasColumnType("uuid") - .HasColumnName("message_id"); - - b.Property("MimeType") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("mime_type"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("PostId") - .HasColumnType("uuid") - .HasColumnName("post_id"); - - b.Property>("SensitiveMarks") - .HasColumnType("jsonb") - .HasColumnName("sensitive_marks"); - - b.Property("Size") - .HasColumnType("bigint") - .HasColumnName("size"); - - b.Property("StorageId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("storage_id"); - - b.Property("StorageUrl") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("storage_url"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("UploadedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("uploaded_at"); - - b.Property("UploadedTo") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("uploaded_to"); - - b.Property>("UserMeta") - .HasColumnType("jsonb") - .HasColumnName("user_meta"); - - b.HasKey("Id") - .HasName("pk_files"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_files_account_id"); - - b.HasIndex("MessageId") - .HasDatabaseName("ix_files_message_id"); - - b.HasIndex("PostId") - .HasDatabaseName("ix_files_post_id"); - - b.ToTable("files", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFileReference", 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("ExpiredAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expired_at"); - - b.Property("FileId") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("file_id"); - - b.Property("ResourceId") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("resource_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("Usage") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("usage"); - - b.HasKey("Id") - .HasName("pk_file_references"); - - b.HasIndex("FileId") - .HasDatabaseName("ix_file_references_file_id"); - - b.ToTable("file_references", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Coupon", 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.Sphere.Wallet.Order", 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("IssuerAppId") - .HasColumnType("uuid") - .HasColumnName("issuer_app_id"); - - b.Property>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("PayeeWalletId") - .HasColumnType("uuid") - .HasColumnName("payee_wallet_id"); - - 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("IssuerAppId") - .HasDatabaseName("ix_payment_orders_issuer_app_id"); - - 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.Sphere.Wallet.Subscription", 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.ToTable("wallet_subscriptions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Transaction", 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.Sphere.Wallet.Wallet", 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.Sphere.Wallet.WalletPocket", 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("PostPostCategory", b => - { - b.Property("CategoriesId") - .HasColumnType("uuid") - .HasColumnName("categories_id"); - - b.Property("PostsId") - .HasColumnType("uuid") - .HasColumnName("posts_id"); - - b.HasKey("CategoriesId", "PostsId") - .HasName("pk_post_category_links"); - - b.HasIndex("PostsId") - .HasDatabaseName("ix_post_category_links_posts_id"); - - b.ToTable("post_category_links", (string)null); - }); - - modelBuilder.Entity("PostPostCollection", b => - { - b.Property("CollectionsId") - .HasColumnType("uuid") - .HasColumnName("collections_id"); - - b.Property("PostsId") - .HasColumnType("uuid") - .HasColumnName("posts_id"); - - b.HasKey("CollectionsId", "PostsId") - .HasName("pk_post_collection_links"); - - b.HasIndex("PostsId") - .HasDatabaseName("ix_post_collection_links_posts_id"); - - b.ToTable("post_collection_links", (string)null); - }); - - modelBuilder.Entity("PostPostTag", b => - { - b.Property("PostsId") - .HasColumnType("uuid") - .HasColumnName("posts_id"); - - b.Property("TagsId") - .HasColumnType("uuid") - .HasColumnName("tags_id"); - - b.HasKey("PostsId", "TagsId") - .HasName("pk_post_tag_links"); - - b.HasIndex("TagsId") - .HasDatabaseName("ix_post_tag_links_tags_id"); - - b.ToTable("post_tag_links", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.AbuseReport", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_abuse_reports_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.AccountAuthFactor", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("AuthFactors") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_auth_factors_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.AccountConnection", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Connections") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_connections_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.AccountContact", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Contacts") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_contacts_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.ActionLog", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_action_logs_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Badge", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Badges") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_badges_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.CheckInResult", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_check_in_results_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.MagicSpell", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .HasConstraintName("fk_magic_spells_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Notification", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_notifications_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.NotificationPushSubscription", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_notification_push_subscriptions_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Profile", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithOne("Profile") - .HasForeignKey("DysonNetwork.Sphere.Account.Profile", "AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_profiles_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Relationship", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("OutgoingRelationships") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_relationships_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Account.Account", "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.Sphere.Account.Status", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_statuses_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Challenge", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Challenges") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_challenges_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Session", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Sessions") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_sessions_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Auth.Challenge", "Challenge") - .WithMany() - .HasForeignKey("ChallengeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_sessions_auth_challenges_challenge_id"); - - b.Navigation("Account"); - - b.Navigation("Challenge"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatMember", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_members_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatRoom", "ChatRoom") - .WithMany("Members") - .HasForeignKey("ChatRoomId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_members_chat_rooms_chat_room_id"); - - b.Navigation("Account"); - - b.Navigation("ChatRoom"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatRoom", b => - { - b.HasOne("DysonNetwork.Sphere.Realm.Realm", "Realm") - .WithMany("ChatRooms") - .HasForeignKey("RealmId") - .HasConstraintName("fk_chat_rooms_realms_realm_id"); - - b.Navigation("Realm"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.Message", b => - { - b.HasOne("DysonNetwork.Sphere.Chat.ChatRoom", "ChatRoom") - .WithMany() - .HasForeignKey("ChatRoomId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_messages_chat_rooms_chat_room_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.Message", "ForwardedMessage") - .WithMany() - .HasForeignKey("ForwardedMessageId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_chat_messages_chat_messages_forwarded_message_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.Message", "RepliedMessage") - .WithMany() - .HasForeignKey("RepliedMessageId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_chat_messages_chat_messages_replied_message_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatMember", "Sender") - .WithMany() - .HasForeignKey("SenderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_messages_chat_members_sender_id"); - - b.Navigation("ChatRoom"); - - b.Navigation("ForwardedMessage"); - - b.Navigation("RepliedMessage"); - - b.Navigation("Sender"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.MessageReaction", b => - { - b.HasOne("DysonNetwork.Sphere.Chat.Message", "Message") - .WithMany("Reactions") - .HasForeignKey("MessageId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_reactions_chat_messages_message_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatMember", "Sender") - .WithMany() - .HasForeignKey("SenderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_reactions_chat_members_sender_id"); - - b.Navigation("Message"); - - b.Navigation("Sender"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.RealtimeCall", b => - { - b.HasOne("DysonNetwork.Sphere.Chat.ChatRoom", "Room") - .WithMany() - .HasForeignKey("RoomId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_realtime_call_chat_rooms_room_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatMember", "Sender") - .WithMany() - .HasForeignKey("SenderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_realtime_call_chat_members_sender_id"); - - b.Navigation("Room"); - - b.Navigation("Sender"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Connection.WebReader.WebArticle", b => - { - b.HasOne("DysonNetwork.Sphere.Connection.WebReader.WebFeed", "Feed") - .WithMany("Articles") - .HasForeignKey("FeedId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_web_articles_web_feeds_feed_id"); - - b.Navigation("Feed"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Connection.WebReader.WebFeed", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany() - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_web_feeds_publishers_publisher_id"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomApp", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Developer") - .WithMany() - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_custom_apps_publishers_publisher_id"); - - b.Navigation("Developer"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomAppSecret", b => - { - b.HasOne("DysonNetwork.Sphere.Developer.CustomApp", "App") - .WithMany() - .HasForeignKey("AppId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_custom_app_secrets_custom_apps_app_id"); - - b.Navigation("App"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroupMember", b => - { - b.HasOne("DysonNetwork.Sphere.Permission.PermissionGroup", "Group") - .WithMany("Members") - .HasForeignKey("GroupId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_permission_group_members_permission_groups_group_id"); - - b.Navigation("Group"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionNode", b => - { - b.HasOne("DysonNetwork.Sphere.Permission.PermissionGroup", "Group") - .WithMany("Nodes") - .HasForeignKey("GroupId") - .HasConstraintName("fk_permission_nodes_permission_groups_group_id"); - - b.Navigation("Group"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.Post", b => - { - b.HasOne("DysonNetwork.Sphere.Post.Post", "ForwardedPost") - .WithMany() - .HasForeignKey("ForwardedPostId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_posts_posts_forwarded_post_id"); - - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Posts") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_posts_publishers_publisher_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", "RepliedPost") - .WithMany() - .HasForeignKey("RepliedPostId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_posts_posts_replied_post_id"); - - b.Navigation("ForwardedPost"); - - b.Navigation("Publisher"); - - b.Navigation("RepliedPost"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostCollection", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Collections") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_collections_publishers_publisher_id"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostReaction", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_reactions_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", "Post") - .WithMany("Reactions") - .HasForeignKey("PostId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_reactions_posts_post_id"); - - b.Navigation("Account"); - - b.Navigation("Post"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.Publisher", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .HasConstraintName("fk_publishers_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Realm.Realm", "Realm") - .WithMany() - .HasForeignKey("RealmId") - .HasConstraintName("fk_publishers_realms_realm_id"); - - b.Navigation("Account"); - - b.Navigation("Realm"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherFeature", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany() - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_features_publishers_publisher_id"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherMember", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_members_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Members") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_members_publishers_publisher_id"); - - b.Navigation("Account"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherSubscription", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_subscriptions_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Subscriptions") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_subscriptions_publishers_publisher_id"); - - b.Navigation("Account"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.Realm", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_realms_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.RealmMember", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_realm_members_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Realm.Realm", "Realm") - .WithMany("Members") - .HasForeignKey("RealmId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_realm_members_realms_realm_id"); - - b.Navigation("Account"); - - b.Navigation("Realm"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.RealmTag", b => - { - b.HasOne("DysonNetwork.Sphere.Realm.Realm", "Realm") - .WithMany("RealmTags") - .HasForeignKey("RealmId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_realm_tags_realms_realm_id"); - - b.HasOne("DysonNetwork.Sphere.Realm.Tag", "Tag") - .WithMany("RealmTags") - .HasForeignKey("TagId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_realm_tags_tags_tag_id"); - - b.Navigation("Realm"); - - b.Navigation("Tag"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.Sticker", b => - { - b.HasOne("DysonNetwork.Sphere.Sticker.StickerPack", "Pack") - .WithMany() - .HasForeignKey("PackId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_stickers_sticker_packs_pack_id"); - - b.Navigation("Pack"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.StickerPack", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany() - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_sticker_packs_publishers_publisher_id"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFile", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_files_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.Message", null) - .WithMany("OutdatedAttachments") - .HasForeignKey("MessageId") - .HasConstraintName("fk_files_chat_messages_message_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany("OutdatedAttachments") - .HasForeignKey("PostId") - .HasConstraintName("fk_files_posts_post_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFileReference", b => - { - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "File") - .WithMany() - .HasForeignKey("FileId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_file_references_files_file_id"); - - b.Navigation("File"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Order", b => - { - b.HasOne("DysonNetwork.Sphere.Developer.CustomApp", "IssuerApp") - .WithMany() - .HasForeignKey("IssuerAppId") - .HasConstraintName("fk_payment_orders_custom_apps_issuer_app_id"); - - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "PayeeWallet") - .WithMany() - .HasForeignKey("PayeeWalletId") - .HasConstraintName("fk_payment_orders_wallets_payee_wallet_id"); - - b.HasOne("DysonNetwork.Sphere.Wallet.Transaction", "Transaction") - .WithMany() - .HasForeignKey("TransactionId") - .HasConstraintName("fk_payment_orders_payment_transactions_transaction_id"); - - b.Navigation("IssuerApp"); - - b.Navigation("PayeeWallet"); - - b.Navigation("Transaction"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Subscription", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Subscriptions") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_wallet_subscriptions_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Wallet.Coupon", "Coupon") - .WithMany() - .HasForeignKey("CouponId") - .HasConstraintName("fk_wallet_subscriptions_wallet_coupons_coupon_id"); - - b.Navigation("Account"); - - b.Navigation("Coupon"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Transaction", b => - { - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "PayeeWallet") - .WithMany() - .HasForeignKey("PayeeWalletId") - .HasConstraintName("fk_payment_transactions_wallets_payee_wallet_id"); - - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "PayerWallet") - .WithMany() - .HasForeignKey("PayerWalletId") - .HasConstraintName("fk_payment_transactions_wallets_payer_wallet_id"); - - b.Navigation("PayeeWallet"); - - b.Navigation("PayerWallet"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Wallet", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_wallets_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.WalletPocket", b => - { - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "Wallet") - .WithMany("Pockets") - .HasForeignKey("WalletId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_wallet_pockets_wallets_wallet_id"); - - b.Navigation("Wallet"); - }); - - modelBuilder.Entity("PostPostCategory", b => - { - b.HasOne("DysonNetwork.Sphere.Post.PostCategory", null) - .WithMany() - .HasForeignKey("CategoriesId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_category_links_post_categories_categories_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany() - .HasForeignKey("PostsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_category_links_posts_posts_id"); - }); - - modelBuilder.Entity("PostPostCollection", b => - { - b.HasOne("DysonNetwork.Sphere.Post.PostCollection", null) - .WithMany() - .HasForeignKey("CollectionsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_collection_links_post_collections_collections_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany() - .HasForeignKey("PostsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_collection_links_posts_posts_id"); - }); - - modelBuilder.Entity("PostPostTag", b => - { - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany() - .HasForeignKey("PostsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_tag_links_posts_posts_id"); - - b.HasOne("DysonNetwork.Sphere.Post.PostTag", null) - .WithMany() - .HasForeignKey("TagsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_tag_links_post_tags_tags_id"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Account", 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"); - - b.Navigation("Subscriptions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatRoom", b => - { - b.Navigation("Members"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.Message", b => - { - b.Navigation("OutdatedAttachments"); - - b.Navigation("Reactions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Connection.WebReader.WebFeed", b => - { - b.Navigation("Articles"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroup", b => - { - b.Navigation("Members"); - - b.Navigation("Nodes"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.Post", b => - { - b.Navigation("OutdatedAttachments"); - - b.Navigation("Reactions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.Publisher", b => - { - b.Navigation("Collections"); - - b.Navigation("Members"); - - b.Navigation("Posts"); - - b.Navigation("Subscriptions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.Realm", b => - { - b.Navigation("ChatRooms"); - - b.Navigation("Members"); - - b.Navigation("RealmTags"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.Tag", b => - { - b.Navigation("RealmTags"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Wallet", b => - { - b.Navigation("Pockets"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/DysonNetwork.Sphere/Migrations/20250626105203_AddRealmTags.cs b/DysonNetwork.Sphere/Migrations/20250626105203_AddRealmTags.cs deleted file mode 100644 index 9782f04..0000000 --- a/DysonNetwork.Sphere/Migrations/20250626105203_AddRealmTags.cs +++ /dev/null @@ -1,84 +0,0 @@ -using System; -using DysonNetwork.Sphere.Connection.WebReader; -using Microsoft.EntityFrameworkCore.Migrations; -using NodaTime; - -#nullable disable - -namespace DysonNetwork.Sphere.Migrations -{ - /// - public partial class AddRealmTags : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "config", - table: "web_feeds", - type: "jsonb", - nullable: false); - - migrationBuilder.CreateTable( - name: "tags", - columns: table => new - { - id = table.Column(type: "uuid", nullable: false), - name = table.Column(type: "character varying(64)", maxLength: 64, nullable: false), - created_at = table.Column(type: "timestamp with time zone", nullable: false), - updated_at = table.Column(type: "timestamp with time zone", nullable: false), - deleted_at = table.Column(type: "timestamp with time zone", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("pk_tags", x => x.id); - }); - - migrationBuilder.CreateTable( - name: "realm_tags", - columns: table => new - { - realm_id = table.Column(type: "uuid", nullable: false), - tag_id = table.Column(type: "uuid", nullable: false), - created_at = table.Column(type: "timestamp with time zone", nullable: false), - updated_at = table.Column(type: "timestamp with time zone", nullable: false), - deleted_at = table.Column(type: "timestamp with time zone", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("pk_realm_tags", x => new { x.realm_id, x.tag_id }); - table.ForeignKey( - name: "fk_realm_tags_realms_realm_id", - column: x => x.realm_id, - principalTable: "realms", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "fk_realm_tags_tags_tag_id", - column: x => x.tag_id, - principalTable: "tags", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateIndex( - name: "ix_realm_tags_tag_id", - table: "realm_tags", - column: "tag_id"); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "realm_tags"); - - migrationBuilder.DropTable( - name: "tags"); - - migrationBuilder.DropColumn( - name: "config", - table: "web_feeds"); - } - } -} diff --git a/DysonNetwork.Sphere/Migrations/AppDatabaseModelSnapshot.cs b/DysonNetwork.Sphere/Migrations/AppDatabaseModelSnapshot.cs deleted file mode 100644 index e252d08..0000000 --- a/DysonNetwork.Sphere/Migrations/AppDatabaseModelSnapshot.cs +++ /dev/null @@ -1,3990 +0,0 @@ -// -using System; -using System.Collections.Generic; -using System.Text.Json; -using DysonNetwork.Sphere; -using DysonNetwork.Sphere.Account; -using DysonNetwork.Sphere.Chat; -using DysonNetwork.Sphere.Connection.WebReader; -using DysonNetwork.Sphere.Developer; -using DysonNetwork.Sphere.Storage; -using DysonNetwork.Sphere.Wallet; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using NetTopologySuite.Geometries; -using NodaTime; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; -using NpgsqlTypes; - -#nullable disable - -namespace DysonNetwork.Sphere.Migrations -{ - [DbContext(typeof(AppDatabase))] - partial class AppDatabaseModelSnapshot : ModelSnapshot - { - protected override void BuildModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "9.0.3") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "postgis"); - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.AbuseReport", 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.Sphere.Account.Account", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("ActivatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("activated_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("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("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.Sphere.Account.AccountAuthFactor", 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.Sphere.Account.AccountConnection", 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.Sphere.Account.AccountContact", 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("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.Sphere.Account.ActionLog", 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("geometry") - .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.Sphere.Account.Badge", 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.Sphere.Account.CheckInResult", 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("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.Sphere.Account.MagicSpell", 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.Sphere.Account.Notification", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("Content") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .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>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("Priority") - .HasColumnType("integer") - .HasColumnName("priority"); - - b.Property("Subtitle") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("subtitle"); - - b.Property("Title") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("title"); - - b.Property("Topic") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("topic"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("ViewedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("viewed_at"); - - b.HasKey("Id") - .HasName("pk_notifications"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_notifications_account_id"); - - b.ToTable("notifications", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.NotificationPushSubscription", 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(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("device_id"); - - b.Property("DeviceToken") - .IsRequired() - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("device_token"); - - b.Property("LastUsedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_used_at"); - - b.Property("Provider") - .HasColumnType("integer") - .HasColumnName("provider"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_notification_push_subscriptions"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_notification_push_subscriptions_account_id"); - - b.HasIndex("DeviceToken", "DeviceId", "AccountId") - .IsUnique() - .HasDatabaseName("ix_notification_push_subscriptions_device_token_device_id_acco"); - - b.ToTable("notification_push_subscriptions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Profile", 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("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_id"); - - 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("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("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - b.Property("Pronouns") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("pronouns"); - - b.Property("StellarMembership") - .HasColumnType("jsonb") - .HasColumnName("stellar_membership"); - - 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("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.Sphere.Account.Relationship", 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.Sphere.Account.Status", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - 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("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("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.Sphere.Auth.Challenge", 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") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("device_id"); - - 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("geometry") - .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("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_challenges"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_auth_challenges_account_id"); - - b.ToTable("auth_challenges", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Session", 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("ChallengeId") - .HasColumnType("uuid") - .HasColumnName("challenge_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("Label") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("label"); - - b.Property("LastGrantedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_granted_at"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_auth_sessions"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_auth_sessions_account_id"); - - b.HasIndex("AppId") - .HasDatabaseName("ix_auth_sessions_app_id"); - - b.HasIndex("ChallengeId") - .HasDatabaseName("ix_auth_sessions_challenge_id"); - - b.ToTable("auth_sessions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatMember", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("BreakUntil") - .HasColumnType("timestamp with time zone") - .HasColumnName("break_until"); - - b.Property("ChatRoomId") - .HasColumnType("uuid") - .HasColumnName("chat_room_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("IsBot") - .HasColumnType("boolean") - .HasColumnName("is_bot"); - - b.Property("JoinedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("joined_at"); - - b.Property("LastReadAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_read_at"); - - b.Property("LeaveAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("leave_at"); - - b.Property("Nick") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("nick"); - - b.Property("Notify") - .HasColumnType("integer") - .HasColumnName("notify"); - - b.Property("Role") - .HasColumnType("integer") - .HasColumnName("role"); - - b.Property("TimeoutCause") - .HasColumnType("jsonb") - .HasColumnName("timeout_cause"); - - b.Property("TimeoutUntil") - .HasColumnType("timestamp with time zone") - .HasColumnName("timeout_until"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_chat_members"); - - b.HasAlternateKey("ChatRoomId", "AccountId") - .HasName("ak_chat_members_chat_room_id_account_id"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_chat_members_account_id"); - - b.ToTable("chat_members", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatRoom", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("Background") - .HasColumnType("jsonb") - .HasColumnName("background"); - - b.Property("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_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("Description") - .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") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("Picture") - .HasColumnType("jsonb") - .HasColumnName("picture"); - - b.Property("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - b.Property("RealmId") - .HasColumnType("uuid") - .HasColumnName("realm_id"); - - b.Property("Type") - .HasColumnType("integer") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_chat_rooms"); - - b.HasIndex("RealmId") - .HasDatabaseName("ix_chat_rooms_realm_id"); - - b.ToTable("chat_rooms", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.Message", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property>("Attachments") - .IsRequired() - .HasColumnType("jsonb") - .HasColumnName("attachments"); - - b.Property("ChatRoomId") - .HasColumnType("uuid") - .HasColumnName("chat_room_id"); - - b.Property("Content") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .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("EditedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("edited_at"); - - b.Property("ForwardedMessageId") - .HasColumnType("uuid") - .HasColumnName("forwarded_message_id"); - - b.Property>("MembersMentioned") - .HasColumnType("jsonb") - .HasColumnName("members_mentioned"); - - b.Property>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("Nonce") - .IsRequired() - .HasMaxLength(36) - .HasColumnType("character varying(36)") - .HasColumnName("nonce"); - - b.Property("RepliedMessageId") - .HasColumnType("uuid") - .HasColumnName("replied_message_id"); - - b.Property("SenderId") - .HasColumnType("uuid") - .HasColumnName("sender_id"); - - 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_chat_messages"); - - b.HasIndex("ChatRoomId") - .HasDatabaseName("ix_chat_messages_chat_room_id"); - - b.HasIndex("ForwardedMessageId") - .HasDatabaseName("ix_chat_messages_forwarded_message_id"); - - b.HasIndex("RepliedMessageId") - .HasDatabaseName("ix_chat_messages_replied_message_id"); - - b.HasIndex("SenderId") - .HasDatabaseName("ix_chat_messages_sender_id"); - - b.ToTable("chat_messages", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.MessageReaction", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("Attitude") - .HasColumnType("integer") - .HasColumnName("attitude"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("MessageId") - .HasColumnType("uuid") - .HasColumnName("message_id"); - - b.Property("SenderId") - .HasColumnType("uuid") - .HasColumnName("sender_id"); - - b.Property("Symbol") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("symbol"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_chat_reactions"); - - b.HasIndex("MessageId") - .HasDatabaseName("ix_chat_reactions_message_id"); - - b.HasIndex("SenderId") - .HasDatabaseName("ix_chat_reactions_sender_id"); - - b.ToTable("chat_reactions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.RealtimeCall", 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("EndedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("ended_at"); - - b.Property("ProviderName") - .HasColumnType("text") - .HasColumnName("provider_name"); - - b.Property("RoomId") - .HasColumnType("uuid") - .HasColumnName("room_id"); - - b.Property("SenderId") - .HasColumnType("uuid") - .HasColumnName("sender_id"); - - b.Property("SessionId") - .HasColumnType("text") - .HasColumnName("session_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("UpstreamConfigJson") - .HasColumnType("jsonb") - .HasColumnName("upstream"); - - b.HasKey("Id") - .HasName("pk_chat_realtime_call"); - - b.HasIndex("RoomId") - .HasDatabaseName("ix_chat_realtime_call_room_id"); - - b.HasIndex("SenderId") - .HasDatabaseName("ix_chat_realtime_call_sender_id"); - - b.ToTable("chat_realtime_call", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Connection.WebReader.WebArticle", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("Author") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("author"); - - b.Property("Content") - .HasColumnType("text") - .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("FeedId") - .HasColumnType("uuid") - .HasColumnName("feed_id"); - - b.Property>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("Preview") - .HasColumnType("jsonb") - .HasColumnName("preview"); - - b.Property("PublishedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("published_at"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("title"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("Url") - .IsRequired() - .HasMaxLength(8192) - .HasColumnType("character varying(8192)") - .HasColumnName("url"); - - b.HasKey("Id") - .HasName("pk_web_articles"); - - b.HasIndex("FeedId") - .HasDatabaseName("ix_web_articles_feed_id"); - - b.HasIndex("Url") - .IsUnique() - .HasDatabaseName("ix_web_articles_url"); - - b.ToTable("web_articles", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Connection.WebReader.WebFeed", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("Config") - .IsRequired() - .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("Description") - .HasMaxLength(8192) - .HasColumnType("character varying(8192)") - .HasColumnName("description"); - - b.Property("Preview") - .HasColumnType("jsonb") - .HasColumnName("preview"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("title"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("Url") - .IsRequired() - .HasMaxLength(8192) - .HasColumnType("character varying(8192)") - .HasColumnName("url"); - - b.HasKey("Id") - .HasName("pk_web_feeds"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_web_feeds_publisher_id"); - - b.HasIndex("Url") - .IsUnique() - .HasDatabaseName("ix_web_feeds_url"); - - b.ToTable("web_feeds", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomApp", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("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") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("Links") - .HasColumnType("jsonb") - .HasColumnName("links"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("OauthConfig") - .HasColumnType("jsonb") - .HasColumnName("oauth_config"); - - b.Property("Picture") - .HasColumnType("jsonb") - .HasColumnName("picture"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("slug"); - - b.Property("Status") - .HasColumnType("integer") - .HasColumnName("status"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("Verification") - .HasColumnType("jsonb") - .HasColumnName("verification"); - - b.HasKey("Id") - .HasName("pk_custom_apps"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_custom_apps_publisher_id"); - - b.ToTable("custom_apps", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomAppSecret", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AppId") - .HasColumnType("uuid") - .HasColumnName("app_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("Description") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("ExpiredAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expired_at"); - - b.Property("IsOidc") - .HasColumnType("boolean") - .HasColumnName("is_oidc"); - - b.Property("Secret") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("secret"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_custom_app_secrets"); - - b.HasIndex("AppId") - .HasDatabaseName("ix_custom_app_secrets_app_id"); - - b.HasIndex("Secret") - .IsUnique() - .HasDatabaseName("ix_custom_app_secrets_secret"); - - b.ToTable("custom_app_secrets", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroup", 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.Sphere.Permission.PermissionGroupMember", 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.Sphere.Permission.PermissionNode", 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("Area") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("area"); - - 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("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", "Area", "Actor") - .HasDatabaseName("ix_permission_nodes_key_area_actor"); - - b.ToTable("permission_nodes", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.Post", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property>("Attachments") - .IsRequired() - .HasColumnType("jsonb") - .HasColumnName("attachments"); - - b.Property("Content") - .HasColumnType("text") - .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("Description") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("Downvotes") - .HasColumnType("integer") - .HasColumnName("downvotes"); - - b.Property("EditedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("edited_at"); - - b.Property("ForwardedPostId") - .HasColumnType("uuid") - .HasColumnName("forwarded_post_id"); - - b.Property("Language") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("language"); - - b.Property>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("PublishedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("published_at"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("RepliedPostId") - .HasColumnType("uuid") - .HasColumnName("replied_post_id"); - - b.Property("SearchVector") - .IsRequired() - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("tsvector") - .HasColumnName("search_vector") - .HasAnnotation("Npgsql:TsVectorConfig", "simple") - .HasAnnotation("Npgsql:TsVectorProperties", new[] { "Title", "Description", "Content" }); - - b.Property>("SensitiveMarks") - .HasColumnType("jsonb") - .HasColumnName("sensitive_marks"); - - b.Property("Title") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("title"); - - b.Property("Type") - .HasColumnType("integer") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("Upvotes") - .HasColumnType("integer") - .HasColumnName("upvotes"); - - b.Property("ViewsTotal") - .HasColumnType("integer") - .HasColumnName("views_total"); - - b.Property("ViewsUnique") - .HasColumnType("integer") - .HasColumnName("views_unique"); - - b.Property("Visibility") - .HasColumnType("integer") - .HasColumnName("visibility"); - - b.HasKey("Id") - .HasName("pk_posts"); - - b.HasIndex("ForwardedPostId") - .HasDatabaseName("ix_posts_forwarded_post_id"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_posts_publisher_id"); - - b.HasIndex("RepliedPostId") - .HasDatabaseName("ix_posts_replied_post_id"); - - b.HasIndex("SearchVector") - .HasDatabaseName("ix_posts_search_vector"); - - NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex("SearchVector"), "GIN"); - - b.ToTable("posts", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostCategory", 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("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("name"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_categories"); - - b.ToTable("post_categories", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostCollection", 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("Description") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("name"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_collections"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_post_collections_publisher_id"); - - b.ToTable("post_collections", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostReaction", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("uuid") - .HasColumnName("account_id"); - - b.Property("Attitude") - .HasColumnType("integer") - .HasColumnName("attitude"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("PostId") - .HasColumnType("uuid") - .HasColumnName("post_id"); - - b.Property("Symbol") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("symbol"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_reactions"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_post_reactions_account_id"); - - b.HasIndex("PostId") - .HasDatabaseName("ix_post_reactions_post_id"); - - b.ToTable("post_reactions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostTag", 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("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("name"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_tags"); - - b.ToTable("post_tags", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.Publisher", 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("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_id"); - - b.Property("Bio") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("bio"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - 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("Picture") - .HasColumnType("jsonb") - .HasColumnName("picture"); - - b.Property("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - b.Property("RealmId") - .HasColumnType("uuid") - .HasColumnName("realm_id"); - - b.Property("Type") - .HasColumnType("integer") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("Verification") - .HasColumnType("jsonb") - .HasColumnName("verification"); - - b.HasKey("Id") - .HasName("pk_publishers"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_publishers_account_id"); - - b.HasIndex("Name") - .IsUnique() - .HasDatabaseName("ix_publishers_name"); - - b.HasIndex("RealmId") - .HasDatabaseName("ix_publishers_realm_id"); - - b.ToTable("publishers", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherFeature", 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("ExpiredAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expired_at"); - - b.Property("Flag") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("flag"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_publisher_features"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_publisher_features_publisher_id"); - - b.ToTable("publisher_features", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherMember", b => - { - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_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("Role") - .HasColumnType("integer") - .HasColumnName("role"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("PublisherId", "AccountId") - .HasName("pk_publisher_members"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_publisher_members_account_id"); - - b.ToTable("publisher_members", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherSubscription", 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("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("Status") - .HasColumnType("integer") - .HasColumnName("status"); - - b.Property("Tier") - .HasColumnType("integer") - .HasColumnName("tier"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_publisher_subscriptions"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_publisher_subscriptions_account_id"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_publisher_subscriptions_publisher_id"); - - b.ToTable("publisher_subscriptions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.Realm", 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("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_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("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("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - - 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("AccountId") - .HasDatabaseName("ix_realms_account_id"); - - b.HasIndex("Slug") - .IsUnique() - .HasDatabaseName("ix_realms_slug"); - - b.ToTable("realms", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.RealmMember", 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.HasIndex("AccountId") - .HasDatabaseName("ix_realm_members_account_id"); - - b.ToTable("realm_members", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.RealmTag", b => - { - b.Property("RealmId") - .HasColumnType("uuid") - .HasColumnName("realm_id"); - - b.Property("TagId") - .HasColumnType("uuid") - .HasColumnName("tag_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("RealmId", "TagId") - .HasName("pk_realm_tags"); - - b.HasIndex("TagId") - .HasDatabaseName("ix_realm_tags_tag_id"); - - b.ToTable("realm_tags", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.Tag", 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("Name") - .IsRequired() - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("name"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_tags"); - - b.ToTable("tags", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.Sticker", 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("Image") - .HasColumnType("jsonb") - .HasColumnName("image"); - - b.Property("ImageId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("image_id"); - - b.Property("PackId") - .HasColumnType("uuid") - .HasColumnName("pack_id"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_stickers"); - - b.HasIndex("PackId") - .HasDatabaseName("ix_stickers_pack_id"); - - b.HasIndex("Slug") - .HasDatabaseName("ix_stickers_slug"); - - b.ToTable("stickers", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.StickerPack", 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("Description") - .IsRequired() - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("Prefix") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("prefix"); - - b.Property("PublisherId") - .HasColumnType("uuid") - .HasColumnName("publisher_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_sticker_packs"); - - b.HasIndex("Prefix") - .IsUnique() - .HasDatabaseName("ix_sticker_packs_prefix"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_sticker_packs_publisher_id"); - - b.ToTable("sticker_packs", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFile", b => - { - b.Property("Id") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .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("Description") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property>("FileMeta") - .HasColumnType("jsonb") - .HasColumnName("file_meta"); - - b.Property("HasCompression") - .HasColumnType("boolean") - .HasColumnName("has_compression"); - - b.Property("Hash") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("hash"); - - b.Property("IsMarkedRecycle") - .HasColumnType("boolean") - .HasColumnName("is_marked_recycle"); - - b.Property("MessageId") - .HasColumnType("uuid") - .HasColumnName("message_id"); - - b.Property("MimeType") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("mime_type"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("PostId") - .HasColumnType("uuid") - .HasColumnName("post_id"); - - b.Property>("SensitiveMarks") - .HasColumnType("jsonb") - .HasColumnName("sensitive_marks"); - - b.Property("Size") - .HasColumnType("bigint") - .HasColumnName("size"); - - b.Property("StorageId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("storage_id"); - - b.Property("StorageUrl") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("storage_url"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("UploadedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("uploaded_at"); - - b.Property("UploadedTo") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("uploaded_to"); - - b.Property>("UserMeta") - .HasColumnType("jsonb") - .HasColumnName("user_meta"); - - b.HasKey("Id") - .HasName("pk_files"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_files_account_id"); - - b.HasIndex("MessageId") - .HasDatabaseName("ix_files_message_id"); - - b.HasIndex("PostId") - .HasDatabaseName("ix_files_post_id"); - - b.ToTable("files", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFileReference", 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("ExpiredAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expired_at"); - - b.Property("FileId") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("file_id"); - - b.Property("ResourceId") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("resource_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("Usage") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("usage"); - - b.HasKey("Id") - .HasName("pk_file_references"); - - b.HasIndex("FileId") - .HasDatabaseName("ix_file_references_file_id"); - - b.ToTable("file_references", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Coupon", 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.Sphere.Wallet.Order", 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("IssuerAppId") - .HasColumnType("uuid") - .HasColumnName("issuer_app_id"); - - b.Property>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("PayeeWalletId") - .HasColumnType("uuid") - .HasColumnName("payee_wallet_id"); - - 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("IssuerAppId") - .HasDatabaseName("ix_payment_orders_issuer_app_id"); - - 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.Sphere.Wallet.Subscription", 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.ToTable("wallet_subscriptions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Transaction", 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.Sphere.Wallet.Wallet", 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.Sphere.Wallet.WalletPocket", 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("PostPostCategory", b => - { - b.Property("CategoriesId") - .HasColumnType("uuid") - .HasColumnName("categories_id"); - - b.Property("PostsId") - .HasColumnType("uuid") - .HasColumnName("posts_id"); - - b.HasKey("CategoriesId", "PostsId") - .HasName("pk_post_category_links"); - - b.HasIndex("PostsId") - .HasDatabaseName("ix_post_category_links_posts_id"); - - b.ToTable("post_category_links", (string)null); - }); - - modelBuilder.Entity("PostPostCollection", b => - { - b.Property("CollectionsId") - .HasColumnType("uuid") - .HasColumnName("collections_id"); - - b.Property("PostsId") - .HasColumnType("uuid") - .HasColumnName("posts_id"); - - b.HasKey("CollectionsId", "PostsId") - .HasName("pk_post_collection_links"); - - b.HasIndex("PostsId") - .HasDatabaseName("ix_post_collection_links_posts_id"); - - b.ToTable("post_collection_links", (string)null); - }); - - modelBuilder.Entity("PostPostTag", b => - { - b.Property("PostsId") - .HasColumnType("uuid") - .HasColumnName("posts_id"); - - b.Property("TagsId") - .HasColumnType("uuid") - .HasColumnName("tags_id"); - - b.HasKey("PostsId", "TagsId") - .HasName("pk_post_tag_links"); - - b.HasIndex("TagsId") - .HasDatabaseName("ix_post_tag_links_tags_id"); - - b.ToTable("post_tag_links", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.AbuseReport", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_abuse_reports_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.AccountAuthFactor", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("AuthFactors") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_auth_factors_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.AccountConnection", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Connections") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_connections_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.AccountContact", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Contacts") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_contacts_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.ActionLog", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_action_logs_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Badge", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Badges") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_badges_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.CheckInResult", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_check_in_results_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.MagicSpell", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .HasConstraintName("fk_magic_spells_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Notification", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_notifications_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.NotificationPushSubscription", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_notification_push_subscriptions_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Profile", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithOne("Profile") - .HasForeignKey("DysonNetwork.Sphere.Account.Profile", "AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_profiles_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Relationship", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("OutgoingRelationships") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_relationships_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Account.Account", "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.Sphere.Account.Status", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_statuses_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Challenge", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Challenges") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_challenges_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Session", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Sessions") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_sessions_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Developer.CustomApp", "App") - .WithMany() - .HasForeignKey("AppId") - .HasConstraintName("fk_auth_sessions_custom_apps_app_id"); - - b.HasOne("DysonNetwork.Sphere.Auth.Challenge", "Challenge") - .WithMany() - .HasForeignKey("ChallengeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_sessions_auth_challenges_challenge_id"); - - b.Navigation("Account"); - - b.Navigation("App"); - - b.Navigation("Challenge"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatMember", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_members_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatRoom", "ChatRoom") - .WithMany("Members") - .HasForeignKey("ChatRoomId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_members_chat_rooms_chat_room_id"); - - b.Navigation("Account"); - - b.Navigation("ChatRoom"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatRoom", b => - { - b.HasOne("DysonNetwork.Sphere.Realm.Realm", "Realm") - .WithMany("ChatRooms") - .HasForeignKey("RealmId") - .HasConstraintName("fk_chat_rooms_realms_realm_id"); - - b.Navigation("Realm"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.Message", b => - { - b.HasOne("DysonNetwork.Sphere.Chat.ChatRoom", "ChatRoom") - .WithMany() - .HasForeignKey("ChatRoomId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_messages_chat_rooms_chat_room_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.Message", "ForwardedMessage") - .WithMany() - .HasForeignKey("ForwardedMessageId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_chat_messages_chat_messages_forwarded_message_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.Message", "RepliedMessage") - .WithMany() - .HasForeignKey("RepliedMessageId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_chat_messages_chat_messages_replied_message_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatMember", "Sender") - .WithMany() - .HasForeignKey("SenderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_messages_chat_members_sender_id"); - - b.Navigation("ChatRoom"); - - b.Navigation("ForwardedMessage"); - - b.Navigation("RepliedMessage"); - - b.Navigation("Sender"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.MessageReaction", b => - { - b.HasOne("DysonNetwork.Sphere.Chat.Message", "Message") - .WithMany("Reactions") - .HasForeignKey("MessageId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_reactions_chat_messages_message_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatMember", "Sender") - .WithMany() - .HasForeignKey("SenderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_reactions_chat_members_sender_id"); - - b.Navigation("Message"); - - b.Navigation("Sender"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.RealtimeCall", b => - { - b.HasOne("DysonNetwork.Sphere.Chat.ChatRoom", "Room") - .WithMany() - .HasForeignKey("RoomId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_realtime_call_chat_rooms_room_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.ChatMember", "Sender") - .WithMany() - .HasForeignKey("SenderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_chat_realtime_call_chat_members_sender_id"); - - b.Navigation("Room"); - - b.Navigation("Sender"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Connection.WebReader.WebArticle", b => - { - b.HasOne("DysonNetwork.Sphere.Connection.WebReader.WebFeed", "Feed") - .WithMany("Articles") - .HasForeignKey("FeedId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_web_articles_web_feeds_feed_id"); - - b.Navigation("Feed"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Connection.WebReader.WebFeed", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany() - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_web_feeds_publishers_publisher_id"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomApp", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Developer") - .WithMany() - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_custom_apps_publishers_publisher_id"); - - b.Navigation("Developer"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomAppSecret", b => - { - b.HasOne("DysonNetwork.Sphere.Developer.CustomApp", "App") - .WithMany("Secrets") - .HasForeignKey("AppId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_custom_app_secrets_custom_apps_app_id"); - - b.Navigation("App"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroupMember", b => - { - b.HasOne("DysonNetwork.Sphere.Permission.PermissionGroup", "Group") - .WithMany("Members") - .HasForeignKey("GroupId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_permission_group_members_permission_groups_group_id"); - - b.Navigation("Group"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionNode", b => - { - b.HasOne("DysonNetwork.Sphere.Permission.PermissionGroup", "Group") - .WithMany("Nodes") - .HasForeignKey("GroupId") - .HasConstraintName("fk_permission_nodes_permission_groups_group_id"); - - b.Navigation("Group"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.Post", b => - { - b.HasOne("DysonNetwork.Sphere.Post.Post", "ForwardedPost") - .WithMany() - .HasForeignKey("ForwardedPostId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_posts_posts_forwarded_post_id"); - - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Posts") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_posts_publishers_publisher_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", "RepliedPost") - .WithMany() - .HasForeignKey("RepliedPostId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_posts_posts_replied_post_id"); - - b.Navigation("ForwardedPost"); - - b.Navigation("Publisher"); - - b.Navigation("RepliedPost"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostCollection", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Collections") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_collections_publishers_publisher_id"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostReaction", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_reactions_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", "Post") - .WithMany("Reactions") - .HasForeignKey("PostId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_reactions_posts_post_id"); - - b.Navigation("Account"); - - b.Navigation("Post"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.Publisher", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .HasConstraintName("fk_publishers_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Realm.Realm", "Realm") - .WithMany() - .HasForeignKey("RealmId") - .HasConstraintName("fk_publishers_realms_realm_id"); - - b.Navigation("Account"); - - b.Navigation("Realm"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherFeature", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Features") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_features_publishers_publisher_id"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherMember", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_members_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Members") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_members_publishers_publisher_id"); - - b.Navigation("Account"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherSubscription", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_subscriptions_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany("Subscriptions") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_subscriptions_publishers_publisher_id"); - - b.Navigation("Account"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.Realm", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_realms_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.RealmMember", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_realm_members_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Realm.Realm", "Realm") - .WithMany("Members") - .HasForeignKey("RealmId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_realm_members_realms_realm_id"); - - b.Navigation("Account"); - - b.Navigation("Realm"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.RealmTag", b => - { - b.HasOne("DysonNetwork.Sphere.Realm.Realm", "Realm") - .WithMany("RealmTags") - .HasForeignKey("RealmId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_realm_tags_realms_realm_id"); - - b.HasOne("DysonNetwork.Sphere.Realm.Tag", "Tag") - .WithMany("RealmTags") - .HasForeignKey("TagId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_realm_tags_tags_tag_id"); - - b.Navigation("Realm"); - - b.Navigation("Tag"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.Sticker", b => - { - b.HasOne("DysonNetwork.Sphere.Sticker.StickerPack", "Pack") - .WithMany() - .HasForeignKey("PackId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_stickers_sticker_packs_pack_id"); - - b.Navigation("Pack"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Sticker.StickerPack", b => - { - b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher") - .WithMany() - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_sticker_packs_publishers_publisher_id"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFile", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_files_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Chat.Message", null) - .WithMany("OutdatedAttachments") - .HasForeignKey("MessageId") - .HasConstraintName("fk_files_chat_messages_message_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany("OutdatedAttachments") - .HasForeignKey("PostId") - .HasConstraintName("fk_files_posts_post_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFileReference", b => - { - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "File") - .WithMany() - .HasForeignKey("FileId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_file_references_files_file_id"); - - b.Navigation("File"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Order", b => - { - b.HasOne("DysonNetwork.Sphere.Developer.CustomApp", "IssuerApp") - .WithMany() - .HasForeignKey("IssuerAppId") - .HasConstraintName("fk_payment_orders_custom_apps_issuer_app_id"); - - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "PayeeWallet") - .WithMany() - .HasForeignKey("PayeeWalletId") - .HasConstraintName("fk_payment_orders_wallets_payee_wallet_id"); - - b.HasOne("DysonNetwork.Sphere.Wallet.Transaction", "Transaction") - .WithMany() - .HasForeignKey("TransactionId") - .HasConstraintName("fk_payment_orders_payment_transactions_transaction_id"); - - b.Navigation("IssuerApp"); - - b.Navigation("PayeeWallet"); - - b.Navigation("Transaction"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Subscription", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Subscriptions") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_wallet_subscriptions_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Wallet.Coupon", "Coupon") - .WithMany() - .HasForeignKey("CouponId") - .HasConstraintName("fk_wallet_subscriptions_wallet_coupons_coupon_id"); - - b.Navigation("Account"); - - b.Navigation("Coupon"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Transaction", b => - { - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "PayeeWallet") - .WithMany() - .HasForeignKey("PayeeWalletId") - .HasConstraintName("fk_payment_transactions_wallets_payee_wallet_id"); - - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "PayerWallet") - .WithMany() - .HasForeignKey("PayerWalletId") - .HasConstraintName("fk_payment_transactions_wallets_payer_wallet_id"); - - b.Navigation("PayeeWallet"); - - b.Navigation("PayerWallet"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Wallet", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_wallets_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.WalletPocket", b => - { - b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "Wallet") - .WithMany("Pockets") - .HasForeignKey("WalletId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_wallet_pockets_wallets_wallet_id"); - - b.Navigation("Wallet"); - }); - - modelBuilder.Entity("PostPostCategory", b => - { - b.HasOne("DysonNetwork.Sphere.Post.PostCategory", null) - .WithMany() - .HasForeignKey("CategoriesId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_category_links_post_categories_categories_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany() - .HasForeignKey("PostsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_category_links_posts_posts_id"); - }); - - modelBuilder.Entity("PostPostCollection", b => - { - b.HasOne("DysonNetwork.Sphere.Post.PostCollection", null) - .WithMany() - .HasForeignKey("CollectionsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_collection_links_post_collections_collections_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany() - .HasForeignKey("PostsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_collection_links_posts_posts_id"); - }); - - modelBuilder.Entity("PostPostTag", b => - { - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany() - .HasForeignKey("PostsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_tag_links_posts_posts_id"); - - b.HasOne("DysonNetwork.Sphere.Post.PostTag", null) - .WithMany() - .HasForeignKey("TagsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_tag_links_post_tags_tags_id"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Account", 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"); - - b.Navigation("Subscriptions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatRoom", b => - { - b.Navigation("Members"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Chat.Message", b => - { - b.Navigation("OutdatedAttachments"); - - b.Navigation("Reactions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Connection.WebReader.WebFeed", b => - { - b.Navigation("Articles"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomApp", b => - { - b.Navigation("Secrets"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroup", b => - { - b.Navigation("Members"); - - b.Navigation("Nodes"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.Post", b => - { - b.Navigation("OutdatedAttachments"); - - b.Navigation("Reactions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Publisher.Publisher", b => - { - b.Navigation("Collections"); - - b.Navigation("Features"); - - b.Navigation("Members"); - - b.Navigation("Posts"); - - b.Navigation("Subscriptions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.Realm", b => - { - b.Navigation("ChatRooms"); - - b.Navigation("Members"); - - b.Navigation("RealmTags"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Realm.Tag", b => - { - b.Navigation("RealmTags"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Wallet", b => - { - b.Navigation("Pockets"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/DysonNetwork.Sphere/Pages/Account/Profile.cshtml b/DysonNetwork.Sphere/Pages/Account/Profile.cshtml deleted file mode 100644 index abdbcef..0000000 --- a/DysonNetwork.Sphere/Pages/Account/Profile.cshtml +++ /dev/null @@ -1,225 +0,0 @@ -@page "//account/profile" -@model DysonNetwork.Sphere.Pages.Account.ProfileModel -@{ - ViewData["Title"] = "Profile"; -} - -@if (Model.Account != null) -{ -
-
- -
-

Profile Settings

-

Manage your account information and preferences

-
- - -
- -
-
-
- -
-
- @Model.Account.Name?[..1].ToUpper() -
-
- - -

@Model.Account.Nick

-

@@@Model.Account.Name

- - -
-
-
Level
-
@Model.Account.Profile.Level
-
-
-
XP
-
@Model.Account.Profile.Experience
-
-
-
Member since
-
@Model.Account.CreatedAt.ToDateTimeUtc().ToString("yyyy/MM")
-
-
-
-
-
- - -
-
- -
-

Profile Information

- -
-
-

Basic Information

-
-
-
Full Name
-
@($"{Model.Account.Profile.FirstName} {Model.Account.Profile.MiddleName} {Model.Account.Profile.LastName}".Trim())
-
-
-
Username
-
@Model.Account.Name
-
-
-
Nickname
-
@Model.Account.Nick
-
-
-
Gender
-
@Model.Account.Profile.Gender
-
-
-
- -
-

Additional Details

-
-
-
Location
-
@Model.Account.Profile.Location
-
-
-
Birthday
-
@Model.Account.Profile.Birthday?.ToString("MMMM d, yyyy", System.Globalization.CultureInfo.InvariantCulture)
-
-
-
Bio
-
@(string.IsNullOrEmpty(Model.Account.Profile.Bio) ? "No bio provided" : Model.Account.Profile.Bio)
-
-
-
-
-
- - -
-

Security Settings

- -
-
-
-

Access Token

-

Use this token to authenticate with the API

-
-
- - -
-
-

Keep this token secure and do not share it with anyone.

-
-
-
-
- - -
-

Active Sessions

-

This is a list of devices that have logged into your account. Revoke any sessions that you do not recognize.

- -
-
-
- - - - - - -
-
-
-
- - - -
-
-
-
Current Session
-
@($"{Request.Headers["User-Agent"]} • {DateTime.Now:MMMM d, yyyy 'at' h:mm tt}")
-
-
-
-
-
- -
-
-
-
-
- - -
-
- -
-
-
-
-
-
-} -else -{ -
-
-
-
- -
-

Profile Not Found

-

User profile not found. Please log in to continue.

- Go to Login -
-
-
-} - -@section Scripts { - -} diff --git a/DysonNetwork.Sphere/Pages/Account/Profile.cshtml.cs b/DysonNetwork.Sphere/Pages/Account/Profile.cshtml.cs deleted file mode 100644 index 1dbb86b..0000000 --- a/DysonNetwork.Sphere/Pages/Account/Profile.cshtml.cs +++ /dev/null @@ -1,28 +0,0 @@ -using DysonNetwork.Sphere.Auth; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.RazorPages; - -namespace DysonNetwork.Sphere.Pages.Account; - -public class ProfileModel : PageModel -{ - public DysonNetwork.Sphere.Account.Account? Account { get; set; } - public string? AccessToken { get; set; } - - public Task OnGetAsync() - { - if (HttpContext.Items["CurrentUser"] is not Sphere.Account.Account currentUser) - return Task.FromResult(RedirectToPage("/Auth/Login")); - - Account = currentUser; - AccessToken = Request.Cookies.TryGetValue(AuthConstants.CookieTokenName, out var value) ? value : null; - - return Task.FromResult(Page()); - } - - public IActionResult OnPostLogout() - { - HttpContext.Response.Cookies.Delete(AuthConstants.CookieTokenName); - return RedirectToPage("/Auth/Login"); - } -} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Pages/Auth/Authorize.cshtml b/DysonNetwork.Sphere/Pages/Auth/Authorize.cshtml deleted file mode 100644 index 3cc992a..0000000 --- a/DysonNetwork.Sphere/Pages/Auth/Authorize.cshtml +++ /dev/null @@ -1,113 +0,0 @@ -@page "/auth/authorize" -@model DysonNetwork.Sphere.Pages.Auth.AuthorizeModel -@{ - ViewData["Title"] = "Authorize Application"; -} - -
-
-
-

- Authorize Application -

- @if (!string.IsNullOrEmpty(Model.AppName)) - { -
-
- @if (!string.IsNullOrEmpty(Model.AppLogo)) - { -
-
- @Model.AppName logo -
-
- } - else - { -
-
- @Model.AppName?[..1].ToUpper() -
-
- } -
-

@Model.AppName

- @if (!string.IsNullOrEmpty(Model.AppUri)) - { - - @Model.AppUri - - } -
-
-
- } -

- When you authorize this application, you consent to the following permissions: -

- -
- -
- -
- - - - - - - - - - - -
- - -
-
-
-
-
- -@functions { - private string GetScopeDisplayName(string scope) - { - return scope switch - { - "openid" => "View your basic profile information", - "profile" => "View your profile information (name, picture, etc.)", - "email" => "View your email address", - "offline_access" => "Access your information while you're not using the app", - _ => scope - }; - } -} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Pages/Auth/Authorize.cshtml.cs b/DysonNetwork.Sphere/Pages/Auth/Authorize.cshtml.cs deleted file mode 100644 index 64249f6..0000000 --- a/DysonNetwork.Sphere/Pages/Auth/Authorize.cshtml.cs +++ /dev/null @@ -1,233 +0,0 @@ -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.RazorPages; -using DysonNetwork.Sphere.Auth.OidcProvider.Services; -using System.ComponentModel.DataAnnotations; -using DysonNetwork.Sphere.Auth; -using DysonNetwork.Sphere.Auth.OidcProvider.Responses; -using DysonNetwork.Sphere.Developer; - -namespace DysonNetwork.Sphere.Pages.Auth; - -public class AuthorizeModel(OidcProviderService oidcService, IConfiguration configuration) : PageModel -{ - [BindProperty(SupportsGet = true)] public string? ReturnUrl { get; set; } - - [BindProperty(SupportsGet = true, Name = "client_id")] - [Required(ErrorMessage = "The client_id parameter is required")] - public string? ClientIdString { get; set; } - - public Guid ClientId { get; set; } - - [BindProperty(SupportsGet = true, Name = "response_type")] - public string ResponseType { get; set; } = "code"; - - [BindProperty(SupportsGet = true, Name = "redirect_uri")] - public string? RedirectUri { get; set; } - - [BindProperty(SupportsGet = true)] public string? Scope { get; set; } - - [BindProperty(SupportsGet = true)] public string? State { get; set; } - - [BindProperty(SupportsGet = true)] public string? Nonce { get; set; } - - - [BindProperty(SupportsGet = true, Name = "code_challenge")] - public string? CodeChallenge { get; set; } - - [BindProperty(SupportsGet = true, Name = "code_challenge_method")] - public string? CodeChallengeMethod { get; set; } - - [BindProperty(SupportsGet = true, Name = "response_mode")] - public string? ResponseMode { get; set; } - - public string? AppName { get; set; } - public string? AppLogo { get; set; } - public string? AppUri { get; set; } - public string[]? RequestedScopes { get; set; } - - public async Task OnGetAsync() - { - // First check if user is authenticated - if (HttpContext.Items["CurrentUser"] is not Sphere.Account.Account currentUser) - { - var returnUrl = Uri.EscapeDataString($"{Request.Path}{Request.QueryString}"); - return RedirectToPage("/Auth/Login", new { returnUrl }); - } - - // Validate client_id - if (string.IsNullOrEmpty(ClientIdString) || !Guid.TryParse(ClientIdString, out var clientId)) - { - ModelState.AddModelError("client_id", "Invalid client_id format"); - return BadRequest("Invalid client_id format"); - } - - ClientId = clientId; - - // Get client info - var client = await oidcService.FindClientByIdAsync(ClientId); - if (client == null) - { - ModelState.AddModelError("client_id", "Client not found"); - return NotFound("Client not found"); - } - - var config = client.OauthConfig; - if (config is null) - { - ModelState.AddModelError("client_id", "Client was not available for use OAuth / OIDC"); - return BadRequest("Client was not enabled for OAuth / OIDC"); - } - - // Validate redirect URI for non-Developing apps - if (client.Status != CustomAppStatus.Developing) - { - if (!string.IsNullOrEmpty(RedirectUri) && !(config.RedirectUris?.Contains(RedirectUri) ?? false)) - { - return BadRequest(new ErrorResponse - { - Error = "invalid_request", - ErrorDescription = "Invalid redirect_uri" - }); - } - } - - // Check for an existing valid session - var existingSession = await oidcService.FindValidSessionAsync(currentUser.Id, clientId); - if (existingSession != null) - { - // Auto-approve since valid session exists - return await HandleApproval(currentUser, client, existingSession); - } - - // Show authorization page - var baseUrl = configuration["BaseUrl"]; - AppName = client.Name; - AppLogo = client.Picture is not null ? $"{baseUrl}/files/{client.Picture.Id}" : null; - AppUri = config.ClientUri; - RequestedScopes = (Scope ?? "openid profile").Split(' ').Distinct().ToArray(); - - return Page(); - } - - private async Task HandleApproval(Sphere.Account.Account currentUser, CustomApp client, Session? existingSession = null) - { - if (string.IsNullOrEmpty(RedirectUri)) - { - ModelState.AddModelError("redirect_uri", "No redirect_uri provided"); - return BadRequest("No redirect_uri provided"); - } - - string authCode; - - if (existingSession != null) - { - // Reuse existing session - authCode = await oidcService.GenerateAuthorizationCodeForReuseSessionAsync( - session: existingSession, - clientId: ClientId, - redirectUri: RedirectUri, - scopes: Scope?.Split(' ', StringSplitOptions.RemoveEmptyEntries) ?? [], - codeChallenge: CodeChallenge, - codeChallengeMethod: CodeChallengeMethod, - nonce: Nonce - ); - } - else - { - // Create a new session (existing flow) - authCode = await oidcService.GenerateAuthorizationCodeAsync( - clientId: ClientId, - userId: currentUser.Id, - redirectUri: RedirectUri, - scopes: Scope?.Split(' ', StringSplitOptions.RemoveEmptyEntries) ?? [], - codeChallenge: CodeChallenge, - codeChallengeMethod: CodeChallengeMethod, - nonce: Nonce - ); - } - - // Build the redirect URI with the authorization code - var redirectUriBuilder = new UriBuilder(RedirectUri); - var query = System.Web.HttpUtility.ParseQueryString(redirectUriBuilder.Query); - query["code"] = authCode; - if (!string.IsNullOrEmpty(State)) - query["state"] = State; - if (!string.IsNullOrEmpty(Scope)) - query["scope"] = Scope; - redirectUriBuilder.Query = query.ToString(); - - return Redirect(redirectUriBuilder.ToString()); - } - - public async Task OnPostAsync(bool allow) - { - if (HttpContext.Items["CurrentUser"] is not Sphere.Account.Account currentUser) return Unauthorized(); - - // First, validate the client ID - if (string.IsNullOrEmpty(ClientIdString) || !Guid.TryParse(ClientIdString, out var clientId)) - { - ModelState.AddModelError("client_id", "Invalid client_id format"); - return BadRequest("Invalid client_id format"); - } - - ClientId = clientId; - - // Check if a client exists - var client = await oidcService.FindClientByIdAsync(ClientId); - if (client == null) - { - ModelState.AddModelError("client_id", "Client not found"); - return NotFound("Client not found"); - } - - if (!allow) - { - // User denied the authorization request - if (string.IsNullOrEmpty(RedirectUri)) - return BadRequest("No redirect_uri provided"); - - var deniedUriBuilder = new UriBuilder(RedirectUri); - var deniedQuery = System.Web.HttpUtility.ParseQueryString(deniedUriBuilder.Query); - deniedQuery["error"] = "access_denied"; - deniedQuery["error_description"] = "The user denied the authorization request"; - if (!string.IsNullOrEmpty(State)) deniedQuery["state"] = State; - deniedUriBuilder.Query = deniedQuery.ToString(); - - return Redirect(deniedUriBuilder.ToString()); - } - - // User approved the request - if (string.IsNullOrEmpty(RedirectUri)) - { - ModelState.AddModelError("redirect_uri", "No redirect_uri provided"); - return BadRequest("No redirect_uri provided"); - } - - // Generate authorization code - var authCode = await oidcService.GenerateAuthorizationCodeAsync( - clientId: ClientId, - userId: currentUser.Id, - redirectUri: RedirectUri, - scopes: Scope?.Split(' ', StringSplitOptions.RemoveEmptyEntries) ?? Array.Empty(), - codeChallenge: CodeChallenge, - codeChallengeMethod: CodeChallengeMethod, - nonce: Nonce); - - // Build the redirect URI with the authorization code - var redirectUri = new UriBuilder(RedirectUri); - var query = System.Web.HttpUtility.ParseQueryString(redirectUri.Query); - - // Add the authorization code - query["code"] = authCode; - - // Add state if provided (for CSRF protection) - if (!string.IsNullOrEmpty(State)) - query["state"] = State; - - // Set the query string - redirectUri.Query = query.ToString(); - - // Redirect back to the client with the authorization code - return Redirect(redirectUri.ToString()); - } -} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Pages/Auth/Callback.cshtml b/DysonNetwork.Sphere/Pages/Auth/Callback.cshtml deleted file mode 100644 index 64e90fe..0000000 --- a/DysonNetwork.Sphere/Pages/Auth/Callback.cshtml +++ /dev/null @@ -1,49 +0,0 @@ -@page "/auth/callback" -@model DysonNetwork.Sphere.Pages.Auth.TokenModel -@{ - ViewData["Title"] = "Authentication Successful"; - Layout = "_Layout"; -} - -
-
-
-

Authentication Successful

-

You can now close this window and return to the application.

-
-
-
- -@section Scripts { - -} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Pages/Auth/Callback.cshtml.cs b/DysonNetwork.Sphere/Pages/Auth/Callback.cshtml.cs deleted file mode 100644 index fdd6e87..0000000 --- a/DysonNetwork.Sphere/Pages/Auth/Callback.cshtml.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Microsoft.AspNetCore.Mvc.RazorPages; - -namespace DysonNetwork.Sphere.Pages.Auth -{ - public class TokenModel : PageModel - { - public void OnGet() - { - } - } -} diff --git a/DysonNetwork.Sphere/Pages/Auth/Challenge.cshtml b/DysonNetwork.Sphere/Pages/Auth/Challenge.cshtml deleted file mode 100644 index d315620..0000000 --- a/DysonNetwork.Sphere/Pages/Auth/Challenge.cshtml +++ /dev/null @@ -1,16 +0,0 @@ -@page "//auth/challenge/{id:guid}" -@model DysonNetwork.Sphere.Pages.Auth.ChallengeModel -@{ - // This page is kept for backward compatibility - // It will automatically redirect to the new SelectFactor page - Response.Redirect($"//auth/challenge/{Model.Id}/select-factor"); -} - -
-
-
- -

Redirecting to authentication page...

-
-
-
diff --git a/DysonNetwork.Sphere/Pages/Auth/Challenge.cshtml.cs b/DysonNetwork.Sphere/Pages/Auth/Challenge.cshtml.cs deleted file mode 100644 index 3c0dc79..0000000 --- a/DysonNetwork.Sphere/Pages/Auth/Challenge.cshtml.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.RazorPages; - -namespace DysonNetwork.Sphere.Pages.Auth -{ - public class ChallengeModel() : PageModel - { - [BindProperty(SupportsGet = true)] - public Guid Id { get; set; } - - [BindProperty(SupportsGet = true)] - public string? ReturnUrl { get; set; } - - public IActionResult OnGet() - { - return RedirectToPage("SelectFactor", new { id = Id, returnUrl = ReturnUrl }); - } - } -} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Pages/Auth/Login.cshtml b/DysonNetwork.Sphere/Pages/Auth/Login.cshtml deleted file mode 100644 index 96e3618..0000000 --- a/DysonNetwork.Sphere/Pages/Auth/Login.cshtml +++ /dev/null @@ -1,40 +0,0 @@ -@page "//auth/login" -@model DysonNetwork.Sphere.Pages.Auth.LoginModel -@{ - ViewData["Title"] = "Login | Solar Network"; - var returnUrl = Model.ReturnUrl ?? ""; -} - -
-
-
-
-

Welcome back!

-

Login to your Solar Network account to continue.

-
- -
- - - -
-
- -
-
- Have no account?
- - Create a new account → - -
-
-
-
-
-
- -@section Scripts { - @{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); } -} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Pages/Auth/Login.cshtml.cs b/DysonNetwork.Sphere/Pages/Auth/Login.cshtml.cs deleted file mode 100644 index 6b12db9..0000000 --- a/DysonNetwork.Sphere/Pages/Auth/Login.cshtml.cs +++ /dev/null @@ -1,93 +0,0 @@ -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.RazorPages; -using System.ComponentModel.DataAnnotations; -using DysonNetwork.Sphere.Auth; -using DysonNetwork.Sphere.Account; -using DysonNetwork.Sphere.Connection; -using NodaTime; -using Microsoft.EntityFrameworkCore; - -namespace DysonNetwork.Sphere.Pages.Auth -{ - public class LoginModel( - AppDatabase db, - AccountService accounts, - AuthService auth, - GeoIpService geo, - ActionLogService als - ) : PageModel - { - [BindProperty] [Required] public string Username { get; set; } = string.Empty; - - [BindProperty] - [FromQuery] - public string? ReturnUrl { get; set; } - - public void OnGet() - { - } - - public async Task OnPostAsync() - { - if (!ModelState.IsValid) - { - return Page(); - } - - var account = await accounts.LookupAccount(Username); - if (account is null) - { - ModelState.AddModelError(string.Empty, "Account was not found."); - return Page(); - } - - // Store the return URL in TempData to preserve it during the login flow - if (!string.IsNullOrEmpty(ReturnUrl) && Url.IsLocalUrl(ReturnUrl)) - { - TempData["ReturnUrl"] = ReturnUrl; - } - - var ipAddress = HttpContext.Connection.RemoteIpAddress?.ToString(); - var userAgent = HttpContext.Request.Headers.UserAgent.ToString(); - var now = Instant.FromDateTimeUtc(DateTime.UtcNow); - - var existingChallenge = await db.AuthChallenges - .Where(e => e.Account == account) - .Where(e => e.IpAddress == ipAddress) - .Where(e => e.UserAgent == userAgent) - .Where(e => e.StepRemain > 0) - .Where(e => e.ExpiredAt != null && now < e.ExpiredAt) - .FirstOrDefaultAsync(); - - if (existingChallenge is not null) - { - return RedirectToPage("Challenge", new { id = existingChallenge.Id }); - } - - var challenge = new Challenge - { - ExpiredAt = Instant.FromDateTimeUtc(DateTime.UtcNow.AddHours(1)), - StepTotal = await auth.DetectChallengeRisk(Request, account), - Platform = ChallengePlatform.Web, - Audiences = new List(), - Scopes = new List(), - IpAddress = ipAddress, - UserAgent = userAgent, - Location = geo.GetPointFromIp(ipAddress), - DeviceId = "web-browser", - AccountId = account.Id - }.Normalize(); - - await db.AuthChallenges.AddAsync(challenge); - await db.SaveChangesAsync(); - - // If we have a return URL, pass it to the verify page - if (TempData.TryGetValue("ReturnUrl", out var returnUrl) && returnUrl is string url) - { - return RedirectToPage("SelectFactor", new { id = challenge.Id, returnUrl = url }); - } - - return RedirectToPage("SelectFactor", new { id = challenge.Id }); - } - } -} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Pages/Auth/SelectFactor.cshtml b/DysonNetwork.Sphere/Pages/Auth/SelectFactor.cshtml deleted file mode 100644 index 61af7d1..0000000 --- a/DysonNetwork.Sphere/Pages/Auth/SelectFactor.cshtml +++ /dev/null @@ -1,127 +0,0 @@ -@page "//auth/challenge/{id:guid}/select-factor" -@using DysonNetwork.Sphere.Account -@model DysonNetwork.Sphere.Pages.Auth.SelectFactorModel -@{ - ViewData["Title"] = "Select Authentication Method | Solar Network"; -} - -
-
-
-
-

Select Authentication Method

- - @if (Model.AuthChallenge != null && Model.AuthChallenge.StepRemain > 0) - { -
-

Progress: @(Model.AuthChallenge.StepTotal - Model.AuthChallenge.StepRemain) of @Model.AuthChallenge.StepTotal steps completed

- -
- } - - @if (Model.AuthChallenge == null) - { -
- - - - Challenge not found or expired. -
- } - else if (Model.AuthChallenge.StepRemain == 0) - { -
- - - - Challenge completed. Redirecting... -
- } - else - { -

Please select an authentication method:

- -
- @foreach (var factor in Model.AuthFactors) - { -
- - - @if (factor.Type == AccountAuthFactorType.EmailCode) - { -
-
-
-

@GetFactorDisplayName(factor.Type)

-

@GetFactorDescription(factor.Type)

-
-
-
- -
- -
-
-
- } - else - { -
-
-
-

@GetFactorDisplayName(factor.Type)

-

@GetFactorDescription(factor.Type)

-
-
- -
-
-
- } -
- } -
- } -
-
-
-
- -@functions { - - private string GetFactorDisplayName(AccountAuthFactorType type) => type switch - { - AccountAuthFactorType.InAppCode => "Authenticator App", - AccountAuthFactorType.EmailCode => "Email", - AccountAuthFactorType.TimedCode => "Timed Code", - AccountAuthFactorType.PinCode => "PIN Code", - AccountAuthFactorType.Password => "Password", - _ => type.ToString() - }; - - private string GetFactorDescription(AccountAuthFactorType type) => type switch - { - AccountAuthFactorType.InAppCode => "Enter a code from your authenticator app", - AccountAuthFactorType.EmailCode => "Receive a verification code via email", - AccountAuthFactorType.TimedCode => "Use a time-based verification code", - AccountAuthFactorType.PinCode => "Enter your PIN code", - AccountAuthFactorType.Password => "Enter your password", - _ => string.Empty - }; - -} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Pages/Auth/SelectFactor.cshtml.cs b/DysonNetwork.Sphere/Pages/Auth/SelectFactor.cshtml.cs deleted file mode 100644 index aed783d..0000000 --- a/DysonNetwork.Sphere/Pages/Auth/SelectFactor.cshtml.cs +++ /dev/null @@ -1,103 +0,0 @@ -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.RazorPages; -using Microsoft.EntityFrameworkCore; -using DysonNetwork.Sphere.Auth; -using DysonNetwork.Sphere.Account; - -namespace DysonNetwork.Sphere.Pages.Auth; - -public class SelectFactorModel( - AppDatabase db, - AccountService accounts -) - : PageModel -{ - [BindProperty(SupportsGet = true)] public Guid Id { get; set; } - [BindProperty(SupportsGet = true)] public string? ReturnUrl { get; set; } - [BindProperty] public Guid SelectedFactorId { get; set; } - [BindProperty] public string? Hint { get; set; } - - public Challenge? AuthChallenge { get; set; } - public List AuthFactors { get; set; } = []; - - public async Task OnGetAsync() - { - await LoadChallengeAndFactors(); - if (AuthChallenge == null) return NotFound(); - if (AuthChallenge.StepRemain == 0) return await ExchangeTokenAndRedirect(); - return Page(); - } - - public async Task OnPostSelectFactorAsync() - { - var challenge = await db.AuthChallenges - .Include(e => e.Account) - .FirstOrDefaultAsync(e => e.Id == Id); - - if (challenge == null) return NotFound(); - - var factor = await db.AccountAuthFactors.FindAsync(SelectedFactorId); - if (factor?.EnabledAt == null || factor.Trustworthy <= 0) - return BadRequest("Invalid authentication method."); - - // Store return URL in TempData to pass to the next step - if (!string.IsNullOrEmpty(ReturnUrl)) - { - TempData["ReturnUrl"] = ReturnUrl; - } - - // For OTP factors that require code delivery - try - { - // For OTP factors that require code delivery - if ( - factor.Type == AccountAuthFactorType.EmailCode - && string.IsNullOrWhiteSpace(Hint) - ) - { - ModelState.AddModelError(string.Empty, - $"Please provide a {factor.Type.ToString().ToLower().Replace("code", "")} to send the code to." - ); - await LoadChallengeAndFactors(); - return Page(); - } - - await accounts.SendFactorCode(challenge.Account, factor, Hint); - } - catch (Exception ex) - { - ModelState.AddModelError(string.Empty, - $"An error occurred while sending the verification code: {ex.Message}"); - await LoadChallengeAndFactors(); - return Page(); - } - - // Redirect to verify page with return URL if available - return !string.IsNullOrEmpty(ReturnUrl) - ? RedirectToPage("VerifyFactor", new { id = Id, factorId = factor.Id, returnUrl = ReturnUrl }) - : RedirectToPage("VerifyFactor", new { id = Id, factorId = factor.Id }); - } - - private async Task LoadChallengeAndFactors() - { - AuthChallenge = await db.AuthChallenges - .Include(e => e.Account) - .FirstOrDefaultAsync(e => e.Id == Id); - - if (AuthChallenge != null) - { - AuthFactors = await db.AccountAuthFactors - .Where(e => e.AccountId == AuthChallenge.Account.Id) - .Where(e => e.EnabledAt != null && e.Trustworthy >= 1) - .ToListAsync(); - } - } - - private async Task ExchangeTokenAndRedirect() - { - // This method is kept for backward compatibility - // The actual token exchange is now handled in the VerifyFactor page - await Task.CompletedTask; // Add this to fix the async warning - return RedirectToPage("/Account/Profile"); - } -} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Pages/Auth/VerifyFactor.cshtml b/DysonNetwork.Sphere/Pages/Auth/VerifyFactor.cshtml deleted file mode 100644 index c02dff9..0000000 --- a/DysonNetwork.Sphere/Pages/Auth/VerifyFactor.cshtml +++ /dev/null @@ -1,99 +0,0 @@ -@page "//auth/challenge/{id:guid}/verify/{factorId:guid}" -@using DysonNetwork.Sphere.Account -@model DysonNetwork.Sphere.Pages.Auth.VerifyFactorModel -@{ - ViewData["Title"] = "Verify Your Identity | Solar Network"; -} - -
-
-
-
-

Verify Your Identity

-

- @switch (Model.FactorType) - { - case AccountAuthFactorType.EmailCode: - We've sent a verification code to your email. - break; - case AccountAuthFactorType.InAppCode: - Enter the code from your authenticator app. - break; - case AccountAuthFactorType.TimedCode: - Enter your time-based verification code. - break; - case AccountAuthFactorType.PinCode: - Enter your PIN code. - break; - case AccountAuthFactorType.Password: - Enter your password. - break; - default: - Please verify your identity. - break; - } -

- - @if (Model.AuthChallenge != null && Model.AuthChallenge.StepRemain > 0) - { -
-

Progress: @(Model.AuthChallenge.StepTotal - Model.AuthChallenge.StepRemain) of @Model.AuthChallenge.StepTotal steps completed

- -
- } - - @if (Model.AuthChallenge == null) - { -
- - Challenge not found or expired. -
- } - else if (Model.AuthChallenge.StepRemain == 0) - { -
- - Verification successful. Redirecting... -
- } - else - { -
- @if (!ViewData.ModelState.IsValid && ViewData.ModelState.Any(m => m.Value.Errors.Any())) - { - - } - -
- - - -
- -
- -
- - -
- } -
-
-
-
- -@section Scripts { - @{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); } -} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Pages/Auth/VerifyFactor.cshtml.cs b/DysonNetwork.Sphere/Pages/Auth/VerifyFactor.cshtml.cs deleted file mode 100644 index 55e2a74..0000000 --- a/DysonNetwork.Sphere/Pages/Auth/VerifyFactor.cshtml.cs +++ /dev/null @@ -1,194 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.RazorPages; -using Microsoft.EntityFrameworkCore; -using DysonNetwork.Sphere.Auth; -using DysonNetwork.Sphere.Account; -using NodaTime; - -namespace DysonNetwork.Sphere.Pages.Auth -{ - public class VerifyFactorModel( - AppDatabase db, - AccountService accounts, - AuthService auth, - ActionLogService als, - IConfiguration configuration, - IHttpClientFactory httpClientFactory - ) - : PageModel - { - [BindProperty(SupportsGet = true)] public Guid Id { get; set; } - - [BindProperty(SupportsGet = true)] public Guid FactorId { get; set; } - - [BindProperty(SupportsGet = true)] public string? ReturnUrl { get; set; } - - [BindProperty, Required] public string Code { get; set; } = string.Empty; - - public Challenge? AuthChallenge { get; set; } - public AccountAuthFactor? Factor { get; set; } - public AccountAuthFactorType FactorType => Factor?.Type ?? AccountAuthFactorType.EmailCode; - - public async Task OnGetAsync() - { - if (!string.IsNullOrEmpty(ReturnUrl)) - { - TempData["ReturnUrl"] = ReturnUrl; - } - await LoadChallengeAndFactor(); - if (AuthChallenge == null) return NotFound("Challenge not found or expired."); - if (Factor == null) return NotFound("Authentication method not found."); - if (AuthChallenge.StepRemain == 0) return await ExchangeTokenAndRedirect(AuthChallenge); - - return Page(); - } - - public async Task OnPostAsync() - { - if (!string.IsNullOrEmpty(ReturnUrl)) - { - TempData["ReturnUrl"] = ReturnUrl; - } - if (!ModelState.IsValid) - { - await LoadChallengeAndFactor(); - return Page(); - } - - await LoadChallengeAndFactor(); - if (AuthChallenge == null) return NotFound("Challenge not found or expired."); - if (Factor == null) return NotFound("Authentication method not found."); - - if (AuthChallenge.BlacklistFactors.Contains(Factor.Id)) - { - ModelState.AddModelError(string.Empty, "This authentication method has already been used for this challenge."); - return Page(); - } - - try - { - if (await accounts.VerifyFactorCode(Factor, Code)) - { - AuthChallenge.StepRemain -= Factor.Trustworthy; - AuthChallenge.StepRemain = Math.Max(0, AuthChallenge.StepRemain); - AuthChallenge.BlacklistFactors.Add(Factor.Id); - db.Update(AuthChallenge); - - als.CreateActionLogFromRequest(ActionLogType.ChallengeSuccess, - new Dictionary - { - { "challenge_id", AuthChallenge.Id }, - { "factor_id", Factor?.Id.ToString() ?? string.Empty } - }, Request, AuthChallenge.Account); - - await db.SaveChangesAsync(); - - if (AuthChallenge.StepRemain == 0) - { - als.CreateActionLogFromRequest(ActionLogType.NewLogin, - new Dictionary - { - { "challenge_id", AuthChallenge.Id }, - { "account_id", AuthChallenge.AccountId } - }, Request, AuthChallenge.Account); - - return await ExchangeTokenAndRedirect(AuthChallenge); - } - - else - { - // If more steps are needed, redirect back to select factor - return RedirectToPage("SelectFactor", new { id = Id, returnUrl = ReturnUrl }); - } - } - else - { - throw new InvalidOperationException("Invalid verification code."); - } - } - catch (Exception ex) - { - if (AuthChallenge != null) - { - AuthChallenge.FailedAttempts++; - db.Update(AuthChallenge); - await db.SaveChangesAsync(); - - als.CreateActionLogFromRequest(ActionLogType.ChallengeFailure, - new Dictionary - { - { "challenge_id", AuthChallenge.Id }, - { "factor_id", Factor?.Id.ToString() ?? string.Empty } - }, Request, AuthChallenge.Account); - } - - - ModelState.AddModelError(string.Empty, ex.Message); - return Page(); - } - } - - private async Task LoadChallengeAndFactor() - { - AuthChallenge = await db.AuthChallenges - .Include(e => e.Account) - .FirstOrDefaultAsync(e => e.Id == Id); - - if (AuthChallenge?.Account != null) - { - Factor = await db.AccountAuthFactors - .FirstOrDefaultAsync(e => e.Id == FactorId && - e.AccountId == AuthChallenge.Account.Id && - e.EnabledAt != null && - e.Trustworthy > 0); - } - } - - private async Task ExchangeTokenAndRedirect(Challenge challenge) - { - await db.Entry(challenge).ReloadAsync(); - if (challenge.StepRemain != 0) return BadRequest($"Challenge not yet completed. Remaining steps: {challenge.StepRemain}"); - - var session = await db.AuthSessions - .FirstOrDefaultAsync(e => e.ChallengeId == challenge.Id); - - if (session == null) - { - session = new Session - { - LastGrantedAt = Instant.FromDateTimeUtc(DateTime.UtcNow), - ExpiredAt = Instant.FromDateTimeUtc(DateTime.UtcNow.AddDays(30)), - Account = challenge.Account, - Challenge = challenge, - }; - db.AuthSessions.Add(session); - await db.SaveChangesAsync(); - } - - var token = auth.CreateToken(session); - Response.Cookies.Append(AuthConstants.CookieTokenName, token, new CookieOptions - { - HttpOnly = true, - Secure = Request.IsHttps, - SameSite = SameSiteMode.Strict, - Path = "/" - }); - - // Redirect to the return URL if provided and valid, otherwise to the home page - if (!string.IsNullOrEmpty(ReturnUrl) && Url.IsLocalUrl(ReturnUrl)) - { - return Redirect(ReturnUrl); - } - - // Check TempData for return URL (in case it was passed through multiple steps) - if (TempData.TryGetValue("ReturnUrl", out var tempReturnUrl) && tempReturnUrl is string returnUrl && - !string.IsNullOrEmpty(returnUrl) && Url.IsLocalUrl(returnUrl)) - { - return Redirect(returnUrl); - } - - return RedirectToPage("/Index"); - } - } -} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Pages/Posts/PostDetail.cshtml b/DysonNetwork.Sphere/Pages/Posts/PostDetail.cshtml index 0fee10e..ed50679 100644 --- a/DysonNetwork.Sphere/Pages/Posts/PostDetail.cshtml +++ b/DysonNetwork.Sphere/Pages/Posts/PostDetail.cshtml @@ -7,14 +7,14 @@ } @section Head { - - + + @if (imageUrl != null) { - + } - - + + }
@@ -23,10 +23,7 @@

@Model.Post.Title

Created at: @Model.Post.CreatedAt - @if (Model.Post.Publisher?.Account != null) - { - by @@@Model.Post.Publisher.Name - } + by @@@Model.Post.Publisher.Name

@Html.Raw(Markdown.ToHtml(Model.Post.Content ?? string.Empty)) @@ -41,7 +38,8 @@
@if (attachment.MimeType != null && attachment.MimeType.StartsWith("image/")) { - @attachment.Name + @attachment.Name } else if (attachment.MimeType != null && attachment.MimeType.StartsWith("video/")) { diff --git a/DysonNetwork.Sphere/Pages/Posts/PostDetail.cshtml.cs b/DysonNetwork.Sphere/Pages/Posts/PostDetail.cshtml.cs index 15d2ba8..27a43d8 100644 --- a/DysonNetwork.Sphere/Pages/Posts/PostDetail.cshtml.cs +++ b/DysonNetwork.Sphere/Pages/Posts/PostDetail.cshtml.cs @@ -1,4 +1,4 @@ -using DysonNetwork.Sphere.Account; +using DysonNetwork.Shared.Proto; using DysonNetwork.Sphere.Post; using DysonNetwork.Sphere.Publisher; using Microsoft.AspNetCore.Mvc; @@ -10,11 +10,10 @@ namespace DysonNetwork.Sphere.Pages.Posts; public class PostDetailModel( AppDatabase db, PublisherService pub, - RelationshipService rels + AccountService.AccountServiceClient accounts ) : PageModel { - [BindProperty(SupportsGet = true)] - public Guid PostId { get; set; } + [BindProperty(SupportsGet = true)] public Guid PostId { get; set; } public Post.Post? Post { get; set; } @@ -22,20 +21,24 @@ public class PostDetailModel( { if (PostId == Guid.Empty) return NotFound(); - + HttpContext.Items.TryGetValue("CurrentUser", out var currentUserValue); - var currentUser = currentUserValue as Sphere.Account.Account; - var userFriends = currentUser is null ? [] : await rels.ListAccountFriends(currentUser); - var userPublishers = currentUser is null ? [] : await pub.GetUserPublishers(currentUser.Id); + var currentUser = currentUserValue as Account; + var accountId = currentUser is null ? Guid.Empty : Guid.Parse(currentUser.Id); + var userFriends = currentUser is null + ? [] + : (await accounts.ListFriendsAsync( + new ListUserRelationshipSimpleRequest { AccountId = currentUser.Id } + )).AccountsId.Select(Guid.Parse).ToList(); + var userPublishers = currentUser is null ? [] : await pub.GetUserPublishers(accountId); Post = await db.Posts - .Where(e => e.Id == PostId) - .Include(e => e.Publisher) - .ThenInclude(p => p.Account) - .Include(e => e.Tags) - .Include(e => e.Categories) - .FilterWithVisibility(currentUser, userFriends, userPublishers) - .FirstOrDefaultAsync(); + .Where(e => e.Id == PostId) + .Include(e => e.Publisher) + .Include(e => e.Tags) + .Include(e => e.Categories) + .FilterWithVisibility(currentUser, userFriends, userPublishers) + .FirstOrDefaultAsync(); if (Post == null) return NotFound(); diff --git a/DysonNetwork.Sphere/Permission/PermissionMiddleware.cs b/DysonNetwork.Sphere/Permission/PermissionMiddleware.cs index a6a6934..b67ba20 100644 --- a/DysonNetwork.Sphere/Permission/PermissionMiddleware.cs +++ b/DysonNetwork.Sphere/Permission/PermissionMiddleware.cs @@ -21,7 +21,7 @@ public class PermissionMiddleware(RequestDelegate next) if (attr != null) { - if (httpContext.Items["CurrentUser"] is not Account.Account currentUser) + if (httpContext.Items["CurrentUser"] is not Account currentUser) { httpContext.Response.StatusCode = StatusCodes.Status403Forbidden; await httpContext.Response.WriteAsync("Unauthorized"); diff --git a/DysonNetwork.Sphere/Post/Post.cs b/DysonNetwork.Sphere/Post/Post.cs index d4acec5..9747f4d 100644 --- a/DysonNetwork.Sphere/Post/Post.cs +++ b/DysonNetwork.Sphere/Post/Post.cs @@ -1,8 +1,9 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using System.Text.Json.Serialization; +using DysonNetwork.Shared.Data; +using DysonNetwork.Shared.Proto; using DysonNetwork.Sphere.Activity; -using DysonNetwork.Sphere.Storage; using NodaTime; using NpgsqlTypes; @@ -52,8 +53,6 @@ public class Post : ModelBase, IIdentifiedResource, IActivity public Guid? ForwardedPostId { get; set; } public Post? ForwardedPost { get; set; } - // Outdated fields, keep for backward compability - public ICollection OutdatedAttachments { get; set; } = new List(); [Column(TypeName = "jsonb")] public List Attachments { get; set; } = []; [JsonIgnore] public NpgsqlTsVector SearchVector { get; set; } = null!; @@ -69,7 +68,7 @@ public class Post : ModelBase, IIdentifiedResource, IActivity [JsonIgnore] public bool Empty => Content == null && Attachments.Count == 0 && ForwardedPostId == null; [NotMapped] public bool IsTruncated { get; set; } = false; - public string ResourceIdentifier => $"post/{Id}"; + public string ResourceIdentifier => $"post:{Id}"; public Activity.Activity ToActivity() { @@ -130,5 +129,4 @@ public class PostReaction : ModelBase public Guid PostId { get; set; } [JsonIgnore] public Post Post { get; set; } = null!; public Guid AccountId { get; set; } - public Account.Account Account { get; set; } = null!; } diff --git a/DysonNetwork.Sphere/Post/PostController.cs b/DysonNetwork.Sphere/Post/PostController.cs index 34da748..7e9dda0 100644 --- a/DysonNetwork.Sphere/Post/PostController.cs +++ b/DysonNetwork.Sphere/Post/PostController.cs @@ -32,7 +32,7 @@ public class PostController( ) { HttpContext.Items.TryGetValue("CurrentUser", out var currentUserValue); - var currentUser = currentUserValue as Account.Account; + var currentUser = currentUserValue as Account; var userFriends = currentUser is null ? [] : await rels.ListAccountFriends(currentUser); var userPublishers = currentUser is null ? [] : await pub.GetUserPublishers(currentUser.Id); @@ -70,7 +70,7 @@ public class PostController( return RedirectToPage("/Posts/PostDetail", new { PostId = id }); HttpContext.Items.TryGetValue("CurrentUser", out var currentUserValue); - var currentUser = currentUserValue as Account.Account; + var currentUser = currentUserValue as Account; var userFriends = currentUser is null ? [] : await rels.ListAccountFriends(currentUser); var userPublishers = currentUser is null ? [] : await pub.GetUserPublishers(currentUser.Id); @@ -102,7 +102,7 @@ public class PostController( return BadRequest("Search query cannot be empty"); HttpContext.Items.TryGetValue("CurrentUser", out var currentUserValue); - var currentUser = currentUserValue as Account.Account; + var currentUser = currentUserValue as Account; var userFriends = currentUser is null ? [] : await rels.ListAccountFriends(currentUser); var userPublishers = currentUser is null ? [] : await pub.GetUserPublishers(currentUser.Id); @@ -139,7 +139,7 @@ public class PostController( [FromQuery] int take = 20) { HttpContext.Items.TryGetValue("CurrentUser", out var currentUserValue); - var currentUser = currentUserValue as Account.Account; + var currentUser = currentUserValue as Account; var userFriends = currentUser is null ? [] : await rels.ListAccountFriends(currentUser); var userPublishers = currentUser is null ? [] : await pub.GetUserPublishers(currentUser.Id); @@ -201,7 +201,7 @@ public class PostController( request.Content = TextSanitizer.Sanitize(request.Content); if (string.IsNullOrWhiteSpace(request.Content) && request.Attachments is { Count: 0 }) return BadRequest("Content is required."); - if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); Publisher.Publisher? publisher; if (publisherName is null) @@ -287,7 +287,7 @@ public class PostController( public async Task> ReactPost(Guid id, [FromBody] PostReactionRequest request) { HttpContext.Items.TryGetValue("CurrentUser", out var currentUserValue); - if (currentUserValue is not Account.Account currentUser) return Unauthorized(); + if (currentUserValue is not Account currentUser) return Unauthorized(); var userFriends = await rels.ListAccountFriends(currentUser); var userPublishers = await pub.GetUserPublishers(currentUser.Id); @@ -336,7 +336,7 @@ public class PostController( request.Content = TextSanitizer.Sanitize(request.Content); if (string.IsNullOrWhiteSpace(request.Content) && request.Attachments is { Count: 0 }) return BadRequest("Content is required."); - if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); var post = await db.Posts .Where(e => e.Id == id) @@ -382,7 +382,7 @@ public class PostController( [HttpDelete("{id:guid}")] public async Task> DeletePost(Guid id) { - if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); var post = await db.Posts .Where(e => e.Id == id) diff --git a/DysonNetwork.Sphere/Post/PostService.cs b/DysonNetwork.Sphere/Post/PostService.cs index 2d0c210..6a208ac 100644 --- a/DysonNetwork.Sphere/Post/PostService.cs +++ b/DysonNetwork.Sphere/Post/PostService.cs @@ -1,9 +1,11 @@ using System.Text.RegularExpressions; -using DysonNetwork.Sphere.Account; -using DysonNetwork.Sphere.Connection.WebReader; +using DysonNetwork.Shared; +using DysonNetwork.Shared.Cache; +using DysonNetwork.Shared.Data; +using DysonNetwork.Shared.Proto; +using DysonNetwork.Sphere.WebReader; using DysonNetwork.Sphere.Localization; using DysonNetwork.Sphere.Publisher; -using DysonNetwork.Sphere.Storage; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Localization; using NodaTime; @@ -12,13 +14,14 @@ namespace DysonNetwork.Sphere.Post; public partial class PostService( AppDatabase db, - FileReferenceService fileRefService, IStringLocalizer localizer, IServiceScopeFactory factory, FlushBufferService flushBuffer, ICacheService cache, - WebReaderService reader, - ILogger logger + ILogger logger, + FileService.FileServiceClient files, + FileReferenceService.FileReferenceServiceClient fileRefs, + WebReaderService reader ) { private const string PostFileUsageIdentifier = "post"; @@ -69,7 +72,7 @@ public partial class PostService( } public async Task PostAsync( - Account.Account user, + Account user, Post post, List? attachments = null, List? tags = null, @@ -91,8 +94,11 @@ public partial class PostService( if (attachments is not null) { - post.Attachments = (await db.Files.Where(e => attachments.Contains(e.Id)).ToListAsync()) - .Select(x => x.ToReferenceObject()).ToList(); + var queryRequest = new GetFileBatchRequest(); + queryRequest.Ids.AddRange(attachments); + var queryResponse = await files.GetFileBatchAsync(queryRequest); + + post.Attachments = queryResponse.Files.Select(CloudFileReferenceObject.FromProtoValue).ToList(); // Re-order the list to match the id list places post.Attachments = attachments .Select(id => post.Attachments.First(a => a.Id == id)) @@ -128,17 +134,16 @@ public partial class PostService( await db.SaveChangesAsync(); // Create file references for each attachment - if (post.Attachments.Any()) + if (post.Attachments.Count != 0) { var postResourceId = $"post:{post.Id}"; - foreach (var file in post.Attachments) + var request = new CreateReferenceBatchRequest { - await fileRefService.CreateReferenceAsync( - file.Id, - PostFileUsageIdentifier, - postResourceId - ); - } + Usage = PostFileUsageIdentifier, + ResourceId = post.ResourceIdentifier, + }; + request.FilesId.AddRange(post.Attachments.Select(a => a.Id)); + await fileRefs.CreateReferenceBatchAsync(request); } if (post.PublishedAt is not null && post.PublishedAt.Value.ToDateTimeUtc() <= DateTime.UtcNow) @@ -157,24 +162,33 @@ public partial class PostService( var sender = post.Publisher; using var scope = factory.CreateScope(); var pub = scope.ServiceProvider.GetRequiredService(); - var nty = scope.ServiceProvider.GetRequiredService(); - var logger = scope.ServiceProvider.GetRequiredService>(); + var nty = scope.ServiceProvider.GetRequiredService(); + var accounts = scope.ServiceProvider.GetRequiredService(); try { var members = await pub.GetPublisherMembers(post.RepliedPost.PublisherId); - foreach (var member in members) + var queryRequest = new GetAccountBatchRequest(); + queryRequest.Id.AddRange(members.Select(m => m.AccountId.ToString())); + var queryResponse = await accounts.GetAccountBatchAsync(queryRequest); + foreach (var member in queryResponse.Accounts) { - AccountService.SetCultureInfo(member.Account); - var (_, content) = ChopPostForNotification(post); - await nty.SendNotification( - member.Account, - "post.replies", - localizer["PostReplyTitle", sender.Nick], - null, - string.IsNullOrWhiteSpace(post.Title) - ? localizer["PostReplyBody", sender.Nick, content] - : localizer["PostReplyContentBody", sender.Nick, post.Title, content], - actionUri: $"/posts/{post.Id}" + if (member is null) continue; + CultureService.SetCultureInfo(member); + await nty.SendPushNotificationToUserAsync( + new SendPushNotificationToUserRequest + { + UserId = member.Id, + Notification = new PushNotification + { + Topic = "post.replies", + Title = localizer["PostReplyTitle", sender.Nick], + Body = string.IsNullOrWhiteSpace(post.Title) + ? localizer["PostReplyBody", sender.Nick, ChopPostForNotification(post).content] + : localizer["PostReplyContentBody", sender.Nick, post.Title, ChopPostForNotification(post).content], + IsSavable = true, + ActionUri = $"/posts/{post.Id}" + } + } ); } } @@ -218,18 +232,20 @@ public partial class PostService( var postResourceId = $"post:{post.Id}"; // Update resource references using the new file list - await fileRefService.UpdateResourceFilesAsync( - postResourceId, - attachments, - PostFileUsageIdentifier - ); + var request = new UpdateResourceFilesRequest + { + ResourceId = postResourceId, + Usage = PostFileUsageIdentifier, + }; + request.FileIds.AddRange(attachments); + await fileRefs.UpdateResourceFilesAsync(request); // Update post attachments by getting files from database - var files = await db.Files - .Where(f => attachments.Contains(f.Id)) - .ToListAsync(); + var queryRequest = new GetFileBatchRequest(); + queryRequest.Ids.AddRange(attachments); + var queryResponse = await files.GetFileBatchAsync(queryRequest); - post.Attachments = files.Select(x => x.ToReferenceObject()).ToList(); + post.Attachments = queryResponse.Files.Select(CloudFileReferenceObject.FromProtoValue).ToList(); } if (tags is not null) @@ -369,10 +385,10 @@ public partial class PostService( public async Task DeletePostAsync(Post post) { - var postResourceId = $"post:{post.Id}"; - // Delete all file references for this post - await fileRefService.DeleteResourceReferencesAsync(postResourceId); + await fileRefs.DeleteResourceReferencesAsync( + new DeleteResourceReferencesRequest { ResourceId = post.ResourceIdentifier } + ); db.Posts.Remove(post); await db.SaveChangesAsync(); @@ -391,7 +407,7 @@ public partial class PostService( public async Task ModifyPostVotes( Post post, PostReaction reaction, - Account.Account sender, + Account sender, bool isRemoving, bool isSelfReact ) @@ -438,24 +454,35 @@ public partial class PostService( { using var scope = factory.CreateScope(); var pub = scope.ServiceProvider.GetRequiredService(); - var nty = scope.ServiceProvider.GetRequiredService(); - var logger = scope.ServiceProvider.GetRequiredService>(); + var nty = scope.ServiceProvider.GetRequiredService(); + var accounts = scope.ServiceProvider.GetRequiredService(); try { var members = await pub.GetPublisherMembers(post.PublisherId); - foreach (var member in members) + var queryRequest = new GetAccountBatchRequest(); + queryRequest.Id.AddRange(members.Select(m => m.AccountId.ToString())); + var queryResponse = await accounts.GetAccountBatchAsync(queryRequest); + foreach (var member in queryResponse.Accounts) { - AccountService.SetCultureInfo(member.Account); - await nty.SendNotification( - member.Account, - "posts.reactions.new", - localizer["PostReactTitle", sender.Nick], - null, - string.IsNullOrWhiteSpace(post.Title) - ? localizer["PostReactBody", sender.Nick, reaction.Symbol] - : localizer["PostReactContentBody", sender.Nick, reaction.Symbol, - post.Title], - actionUri: $"/posts/{post.Id}" + if (member is null) continue; + CultureService.SetCultureInfo(member); + + await nty.SendPushNotificationToUserAsync( + new SendPushNotificationToUserRequest + { + UserId = member.Id, + Notification = new PushNotification + { + Topic = "posts.reactions.new", + Title = localizer["PostReactTitle", sender.Nick], + Body = string.IsNullOrWhiteSpace(post.Title) + ? localizer["PostReactBody", sender.Nick, reaction.Symbol] + : localizer["PostReactContentBody", sender.Nick, reaction.Symbol, + post.Title], + IsSavable = true, + ActionUri = $"/posts/{post.Id}" + } + } ); } } @@ -563,7 +590,7 @@ public partial class PostService( return posts; } - public async Task> LoadInteractive(List posts, Account.Account? currentUser = null) + public async Task> LoadInteractive(List posts, Account? currentUser = null) { if (posts.Count == 0) return posts; @@ -586,7 +613,7 @@ public partial class PostService( // Track view for each post in the list if (currentUser != null) - await IncreaseViewCount(post.Id, currentUser.Id.ToString()); + await IncreaseViewCount(post.Id, currentUser.Id); else await IncreaseViewCount(post.Id); } @@ -605,8 +632,11 @@ public partial class PostService( ); } - public async Task> LoadPostInfo(List posts, Account.Account? currentUser = null, - bool truncate = false) + public async Task> LoadPostInfo( + List posts, + Account? currentUser = null, + bool truncate = false + ) { if (posts.Count == 0) return posts; @@ -619,7 +649,7 @@ public partial class PostService( return posts; } - public async Task LoadPostInfo(Post post, Account.Account? currentUser = null, bool truncate = false) + public async Task LoadPostInfo(Post post, Account? currentUser = null, bool truncate = false) { // Convert single post to list, process it, then return the single post var posts = await LoadPostInfo([post], currentUser, truncate); @@ -631,7 +661,7 @@ public static class PostQueryExtensions { public static IQueryable FilterWithVisibility( this IQueryable source, - Account.Account? currentUser, + Account? currentUser, List userFriends, List publishers, bool isListing = false diff --git a/DysonNetwork.Sphere/Publisher/Publisher.cs b/DysonNetwork.Sphere/Publisher/Publisher.cs index bae02ad..30975e4 100644 --- a/DysonNetwork.Sphere/Publisher/Publisher.cs +++ b/DysonNetwork.Sphere/Publisher/Publisher.cs @@ -1,8 +1,8 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using System.Text.Json.Serialization; +using DysonNetwork.Shared.Data; using DysonNetwork.Sphere.Post; -using DysonNetwork.Sphere.Storage; using Microsoft.EntityFrameworkCore; using NodaTime; @@ -30,7 +30,7 @@ public class Publisher : ModelBase, IIdentifiedResource [Column(TypeName = "jsonb")] public CloudFileReferenceObject? Picture { get; set; } [Column(TypeName = "jsonb")] public CloudFileReferenceObject? Background { get; set; } - [Column(TypeName = "jsonb")] public Account.VerificationMark? Verification { get; set; } + [Column(TypeName = "jsonb")] public VerificationMark? Verification { get; set; } [JsonIgnore] public ICollection Posts { get; set; } = new List(); [JsonIgnore] public ICollection Collections { get; set; } = new List(); @@ -41,11 +41,10 @@ public class Publisher : ModelBase, IIdentifiedResource public ICollection Subscriptions { get; set; } = new List(); public Guid? AccountId { get; set; } - public Account.Account? Account { get; set; } public Guid? RealmId { get; set; } [JsonIgnore] public Realm.Realm? Realm { get; set; } - public string ResourceIdentifier => $"publisher/{Id}"; + public string ResourceIdentifier => $"publisher:{Id}"; } public enum PublisherMemberRole @@ -61,7 +60,6 @@ public class PublisherMember : ModelBase public Guid PublisherId { get; set; } [JsonIgnore] public Publisher Publisher { get; set; } = null!; public Guid AccountId { get; set; } - public Account.Account Account { get; set; } = null!; public PublisherMemberRole Role { get; set; } = PublisherMemberRole.Viewer; public Instant? JoinedAt { get; set; } @@ -81,7 +79,6 @@ public class PublisherSubscription : ModelBase public Guid PublisherId { get; set; } [JsonIgnore] public Publisher Publisher { get; set; } = null!; public Guid AccountId { get; set; } - [JsonIgnore] public Account.Account Account { get; set; } = null!; public PublisherSubscriptionStatus Status { get; set; } = PublisherSubscriptionStatus.Active; public int Tier { get; set; } = 0; diff --git a/DysonNetwork.Sphere/Publisher/PublisherController.cs b/DysonNetwork.Sphere/Publisher/PublisherController.cs index 36dece4..aaa2ba8 100644 --- a/DysonNetwork.Sphere/Publisher/PublisherController.cs +++ b/DysonNetwork.Sphere/Publisher/PublisherController.cs @@ -49,7 +49,7 @@ public class PublisherController( [Authorize] public async Task>> ListManagedPublishers() { - if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); var userId = currentUser.Id; var members = await db.PublisherMembers @@ -65,7 +65,7 @@ public class PublisherController( [Authorize] public async Task>> ListInvites() { - if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); var userId = currentUser.Id; var members = await db.PublisherMembers @@ -88,7 +88,7 @@ public class PublisherController( public async Task> InviteMember(string name, [FromBody] PublisherMemberRequest request) { - if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); var userId = currentUser.Id; var relatedUser = await db.Accounts.FindAsync(request.RelatedUserId); @@ -128,7 +128,7 @@ public class PublisherController( [Authorize] public async Task> AcceptMemberInvite(string name) { - if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); var userId = currentUser.Id; var member = await db.PublisherMembers @@ -154,7 +154,7 @@ public class PublisherController( [Authorize] public async Task DeclineMemberInvite(string name) { - if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); var userId = currentUser.Id; var member = await db.PublisherMembers @@ -179,7 +179,7 @@ public class PublisherController( [Authorize] public async Task RemoveMember(string name, Guid memberId) { - if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); var publisher = await db.Publishers .Where(p => p.Name == name) @@ -224,7 +224,7 @@ public class PublisherController( [RequiredPermission("global", "publishers.create")] public async Task> CreatePublisherIndividual([FromBody] PublisherRequest request) { - if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); var takenName = request.Name ?? currentUser.Name; var duplicateNameCount = await db.Publishers @@ -274,7 +274,7 @@ public class PublisherController( public async Task> CreatePublisherOrganization(string realmSlug, [FromBody] PublisherRequest request) { - if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); var realm = await db.Realms.FirstOrDefaultAsync(r => r.Slug == realmSlug); if (realm == null) return NotFound("Realm not found"); @@ -328,7 +328,7 @@ public class PublisherController( [Authorize] public async Task> UpdatePublisher(string name, PublisherRequest request) { - if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); var userId = currentUser.Id; var publisher = await db.Publishers @@ -405,7 +405,7 @@ public class PublisherController( [Authorize] public async Task> DeletePublisher(string name) { - if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); var userId = currentUser.Id; var publisher = await db.Publishers @@ -473,7 +473,7 @@ public class PublisherController( [Authorize] public async Task> GetCurrentIdentity(string name) { - if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); var userId = currentUser.Id; var publisher = await db.Publishers diff --git a/DysonNetwork.Sphere/Publisher/PublisherService.cs b/DysonNetwork.Sphere/Publisher/PublisherService.cs index 188829a..db7300c 100644 --- a/DysonNetwork.Sphere/Publisher/PublisherService.cs +++ b/DysonNetwork.Sphere/Publisher/PublisherService.cs @@ -1,12 +1,17 @@ +using DysonNetwork.Shared.Cache; +using DysonNetwork.Shared.Data; +using DysonNetwork.Shared.Proto; using DysonNetwork.Sphere.Post; -using DysonNetwork.Sphere.Storage; using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Caching.Memory; using NodaTime; namespace DysonNetwork.Sphere.Publisher; -public class PublisherService(AppDatabase db, FileReferenceService fileRefService, ICacheService cache) +public class PublisherService( + AppDatabase db, + FileReferenceService.FileReferenceServiceClient fileRefs, + ICacheService cache +) { public async Task GetPublisherByName(string name) { @@ -20,12 +25,12 @@ public class PublisherService(AppDatabase db, FileReferenceService fileRefServic public async Task> GetUserPublishers(Guid userId) { var cacheKey = string.Format(UserPublishersCacheKey, userId); - + // Try to get publishers from the cache first var publishers = await cache.GetAsync>(cacheKey); if (publishers is not null) return publishers; - + // If not in cache, fetch from a database var publishersId = await db.PublisherMembers .Where(p => p.AccountId == userId) @@ -34,10 +39,10 @@ public class PublisherService(AppDatabase db, FileReferenceService fileRefServic publishers = await db.Publishers .Where(p => publishersId.Contains(p.Id)) .ToListAsync(); - + // Store in a cache for 5 minutes await cache.SetAsync(cacheKey, publishers, TimeSpan.FromMinutes(5)); - + return publishers; } @@ -92,11 +97,10 @@ public class PublisherService(AppDatabase db, FileReferenceService fileRefServic return result; } - - + public const string SubscribedPublishersCacheKey = "accounts:{0}:subscribed-publishers"; - + public async Task> GetSubscribedPublishers(Guid userId) { var cacheKey = string.Format(SubscribedPublishersCacheKey, userId); @@ -127,32 +131,30 @@ public class PublisherService(AppDatabase db, FileReferenceService fileRefServic public async Task> GetPublisherMembers(Guid publisherId) { var cacheKey = string.Format(PublisherMembersCacheKey, publisherId); - + // Try to get members from the cache first var members = await cache.GetAsync>(cacheKey); if (members is not null) return members; - + // If not in cache, fetch from a database members = await db.PublisherMembers .Where(p => p.PublisherId == publisherId) - .Include(p => p.Account) - .ThenInclude(p => p.Profile) .ToListAsync(); - + // Store in cache for 5 minutes (consistent with other cache durations in the class) await cache.SetAsync(cacheKey, members, TimeSpan.FromMinutes(5)); - + return members; } - + public async Task CreateIndividualPublisher( - Account.Account account, + Account account, string? name, string? nick, string? bio, - CloudFile? picture, - CloudFile? background + CloudFileReferenceObject? picture, + CloudFileReferenceObject? background ) { var publisher = new Publisher @@ -161,14 +163,14 @@ public class PublisherService(AppDatabase db, FileReferenceService fileRefServic Name = name ?? account.Name, Nick = nick ?? account.Nick, Bio = bio ?? account.Profile.Bio, - Picture = picture?.ToReferenceObject() ?? account.Profile.Picture, - Background = background?.ToReferenceObject() ?? account.Profile.Background, - AccountId = account.Id, + Picture = picture ?? CloudFileReferenceObject.FromProtoValue(account.Profile.Picture), + Background = background ?? CloudFileReferenceObject.FromProtoValue(account.Profile.Background), + AccountId = Guid.Parse(account.Id), Members = new List { new() { - AccountId = account.Id, + AccountId = Guid.Parse(account.Id), Role = PublisherMemberRole.Owner, JoinedAt = Instant.FromDateTimeUtc(DateTime.UtcNow) } @@ -178,21 +180,26 @@ public class PublisherService(AppDatabase db, FileReferenceService fileRefServic db.Publishers.Add(publisher); await db.SaveChangesAsync(); - var publisherResourceId = $"publisher:{publisher.Id}"; - - if (publisher.Picture is not null) { - await fileRefService.CreateReferenceAsync( - publisher.Picture.Id, - "publisher.picture", - publisherResourceId + if (publisher.Picture is not null) + { + await fileRefs.CreateReferenceAsync( + new CreateReferenceRequest + { + FileId = publisher.Picture.Id, + Usage = "publisher.picture", + ResourceId = publisher.ResourceIdentifier, + } ); } - - if (publisher.Background is not null) { - await fileRefService.CreateReferenceAsync( - publisher.Background.Id, - "publisher.background", - publisherResourceId + if (publisher.Background is not null) + { + await fileRefs.CreateReferenceAsync( + new CreateReferenceRequest + { + FileId = publisher.Background.Id, + Usage = "publisher.background", + ResourceId = publisher.ResourceIdentifier, + } ); } @@ -201,12 +208,12 @@ public class PublisherService(AppDatabase db, FileReferenceService fileRefServic public async Task CreateOrganizationPublisher( Realm.Realm realm, - Account.Account account, + Account account, string? name, string? nick, string? bio, - CloudFile? picture, - CloudFile? background + CloudFileReferenceObject? picture, + CloudFileReferenceObject? background ) { var publisher = new Publisher @@ -215,14 +222,14 @@ public class PublisherService(AppDatabase db, FileReferenceService fileRefServic Name = name ?? realm.Slug, Nick = nick ?? realm.Name, Bio = bio ?? realm.Description, - Picture = picture?.ToReferenceObject() ?? realm.Picture, - Background = background?.ToReferenceObject() ?? realm.Background, + Picture = picture ?? CloudFileReferenceObject.FromProtoValue(account.Profile.Picture), + Background = background ?? CloudFileReferenceObject.FromProtoValue(account.Profile.Background), RealmId = realm.Id, Members = new List { new() { - AccountId = account.Id, + AccountId = Guid.Parse(account.Id), Role = PublisherMemberRole.Owner, JoinedAt = Instant.FromDateTimeUtc(DateTime.UtcNow) } @@ -232,21 +239,26 @@ public class PublisherService(AppDatabase db, FileReferenceService fileRefServic db.Publishers.Add(publisher); await db.SaveChangesAsync(); - var publisherResourceId = $"publisher:{publisher.Id}"; - - if (publisher.Picture is not null) { - await fileRefService.CreateReferenceAsync( - publisher.Picture.Id, - "publisher.picture", - publisherResourceId + if (publisher.Picture is not null) + { + await fileRefs.CreateReferenceAsync( + new CreateReferenceRequest + { + FileId = publisher.Picture.Id, + Usage = "publisher.picture", + ResourceId = publisher.ResourceIdentifier, + } ); } - - if (publisher.Background is not null) { - await fileRefService.CreateReferenceAsync( - publisher.Background.Id, - "publisher.background", - publisherResourceId + if (publisher.Background is not null) + { + await fileRefs.CreateReferenceAsync( + new CreateReferenceRequest + { + FileId = publisher.Background.Id, + Usage = "publisher.background", + ResourceId = publisher.ResourceIdentifier, + } ); } diff --git a/DysonNetwork.Sphere/Publisher/PublisherSubscriptionController.cs b/DysonNetwork.Sphere/Publisher/PublisherSubscriptionController.cs index 727386f..b5270c4 100644 --- a/DysonNetwork.Sphere/Publisher/PublisherSubscriptionController.cs +++ b/DysonNetwork.Sphere/Publisher/PublisherSubscriptionController.cs @@ -30,7 +30,7 @@ public class PublisherSubscriptionController( [Authorize] public async Task> CheckSubscriptionStatus(string name) { - if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); // Check if the publisher exists var publisher = await db.Publishers.FirstOrDefaultAsync(p => p.Name == name); @@ -53,7 +53,7 @@ public class PublisherSubscriptionController( string name, [FromBody] SubscribeRequest request) { - if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); // Check if the publisher exists var publisher = await db.Publishers.FirstOrDefaultAsync(p => p.Name == name); @@ -81,7 +81,7 @@ public class PublisherSubscriptionController( [Authorize] public async Task Unsubscribe(string name) { - if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); // Check if the publisher exists var publisher = await db.Publishers.FirstOrDefaultAsync(e => e.Name == name); @@ -104,7 +104,7 @@ public class PublisherSubscriptionController( [Authorize] public async Task>> GetCurrentSubscriptions() { - if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); var subscriptions = await subs.GetAccountSubscriptionsAsync(currentUser.Id); return subscriptions; diff --git a/DysonNetwork.Sphere/Realm/Realm.cs b/DysonNetwork.Sphere/Realm/Realm.cs index 9a26322..fdd5d8c 100644 --- a/DysonNetwork.Sphere/Realm/Realm.cs +++ b/DysonNetwork.Sphere/Realm/Realm.cs @@ -32,9 +32,8 @@ public class Realm : ModelBase, IIdentifiedResource [JsonIgnore] public ICollection RealmTags { get; set; } = new List(); public Guid AccountId { get; set; } - [JsonIgnore] public Account.Account Account { get; set; } = null!; - public string ResourceIdentifier => $"realm/{Id}"; + public string ResourceIdentifier => $"realm:{Id}"; } public abstract class RealmMemberRole @@ -49,7 +48,7 @@ public class RealmMember : ModelBase public Guid RealmId { get; set; } public Realm Realm { get; set; } = null!; public Guid AccountId { get; set; } - public Account.Account Account { get; set; } = null!; + public Account Account { get; set; } = null!; public int Role { get; set; } = RealmMemberRole.Normal; public Instant? JoinedAt { get; set; } diff --git a/DysonNetwork.Sphere/Realm/RealmChatController.cs b/DysonNetwork.Sphere/Realm/RealmChatController.cs index 90d8fcf..61567e6 100644 --- a/DysonNetwork.Sphere/Realm/RealmChatController.cs +++ b/DysonNetwork.Sphere/Realm/RealmChatController.cs @@ -13,7 +13,7 @@ public class RealmChatController(AppDatabase db, RealmService rs) : ControllerBa [Authorize] public async Task>> ListRealmChat(string slug) { - var currentUser = HttpContext.Items["CurrentUser"] as Account.Account; + var currentUser = HttpContext.Items["CurrentUser"] as Account; var realm = await db.Realms .Where(r => r.Slug == slug) diff --git a/DysonNetwork.Sphere/Realm/RealmController.cs b/DysonNetwork.Sphere/Realm/RealmController.cs index 6e741c2..adc0603 100644 --- a/DysonNetwork.Sphere/Realm/RealmController.cs +++ b/DysonNetwork.Sphere/Realm/RealmController.cs @@ -34,7 +34,7 @@ public class RealmController( [Authorize] public async Task>> ListJoinedRealms() { - if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); var userId = currentUser.Id; var members = await db.RealmMembers @@ -52,7 +52,7 @@ public class RealmController( [Authorize] public async Task>> ListInvites() { - if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); var userId = currentUser.Id; var members = await db.RealmMembers @@ -75,7 +75,7 @@ public class RealmController( public async Task> InviteMember(string slug, [FromBody] RealmMemberRequest request) { - if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); var userId = currentUser.Id; var relatedUser = await db.Accounts.FindAsync(request.RelatedUserId); @@ -126,7 +126,7 @@ public class RealmController( [Authorize] public async Task> AcceptMemberInvite(string slug) { - if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); var userId = currentUser.Id; var member = await db.RealmMembers @@ -153,7 +153,7 @@ public class RealmController( [Authorize] public async Task DeclineMemberInvite(string slug) { - if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); var userId = currentUser.Id; var member = await db.RealmMembers @@ -192,7 +192,7 @@ public class RealmController( if (!realm.IsPublic) { - if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); if (!await rs.IsMemberWithRole(realm.Id, currentUser.Id, RealmMemberRole.Normal)) return StatusCode(403, "You must be a member to view this realm's members."); } @@ -249,7 +249,7 @@ public class RealmController( [Authorize] public async Task> GetCurrentIdentity(string slug) { - if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); var userId = currentUser.Id; var member = await db.RealmMembers @@ -267,7 +267,7 @@ public class RealmController( [Authorize] public async Task LeaveRealm(string slug) { - if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); var userId = currentUser.Id; var member = await db.RealmMembers @@ -307,7 +307,7 @@ public class RealmController( [Authorize] public async Task> CreateRealm(RealmRequest request) { - if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); if (string.IsNullOrWhiteSpace(request.Name)) return BadRequest("You cannot create a realm without a name."); if (string.IsNullOrWhiteSpace(request.Slug)) return BadRequest("You cannot create a realm without a slug."); @@ -380,7 +380,7 @@ public class RealmController( [Authorize] public async Task> Update(string slug, [FromBody] RealmRequest request) { - if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); var realm = await db.Realms .Where(r => r.Slug == slug) @@ -466,7 +466,7 @@ public class RealmController( [Authorize] public async Task> JoinRealm(string slug) { - if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); var realm = await db.Realms .Where(r => r.Slug == slug) @@ -506,7 +506,7 @@ public class RealmController( [Authorize] public async Task RemoveMember(string slug, Guid memberId) { - if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); var realm = await db.Realms .Where(r => r.Slug == slug) @@ -538,7 +538,7 @@ public class RealmController( public async Task> UpdateMemberRole(string slug, Guid memberId, [FromBody] int newRole) { if (newRole >= RealmMemberRole.Owner) return BadRequest("Unable to set realm member to owner or greater role."); - if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); var realm = await db.Realms .Where(r => r.Slug == slug) @@ -572,7 +572,7 @@ public class RealmController( [Authorize] public async Task Delete(string slug) { - if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); var realm = await db.Realms .Where(r => r.Slug == slug) diff --git a/DysonNetwork.Sphere/Safety/AbuseReportController.cs b/DysonNetwork.Sphere/Safety/AbuseReportController.cs index e1c9ed6..fdaba61 100644 --- a/DysonNetwork.Sphere/Safety/AbuseReportController.cs +++ b/DysonNetwork.Sphere/Safety/AbuseReportController.cs @@ -30,7 +30,7 @@ public class AbuseReportController( [ProducesResponseType(StatusCodes.Status400BadRequest)] public async Task> CreateReport([FromBody] CreateReportRequest request) { - if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); try { @@ -75,7 +75,7 @@ public class AbuseReportController( [FromQuery] bool includeResolved = false ) { - if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); var totalCount = await safety.CountUserReports(currentUser.Id, includeResolved); var reports = await safety.GetUserReports(currentUser.Id, offset, take, includeResolved); @@ -101,7 +101,7 @@ public class AbuseReportController( [ProducesResponseType(StatusCodes.Status403Forbidden)] public async Task> GetMyReportById(Guid id) { - if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); var report = await safety.GetReportById(id); if (report == null) return NotFound(); diff --git a/DysonNetwork.Sphere/Startup/ScheduledJobsConfiguration.cs b/DysonNetwork.Sphere/Startup/ScheduledJobsConfiguration.cs index 8746f28..43cd0b1 100644 --- a/DysonNetwork.Sphere/Startup/ScheduledJobsConfiguration.cs +++ b/DysonNetwork.Sphere/Startup/ScheduledJobsConfiguration.cs @@ -1,5 +1,5 @@ using DysonNetwork.Sphere.Storage; -using DysonNetwork.Sphere.Connection.WebReader; +using DysonNetwork.Sphere.WebReader; using DysonNetwork.Sphere.Storage.Handlers; using DysonNetwork.Sphere.Wallet; using Quartz; diff --git a/DysonNetwork.Sphere/Startup/ServiceCollectionExtensions.cs b/DysonNetwork.Sphere/Startup/ServiceCollectionExtensions.cs index 4db2772..31d083f 100644 --- a/DysonNetwork.Sphere/Startup/ServiceCollectionExtensions.cs +++ b/DysonNetwork.Sphere/Startup/ServiceCollectionExtensions.cs @@ -1,22 +1,12 @@ using System.Globalization; -using DysonNetwork.Sphere.Account; using DysonNetwork.Sphere.Activity; -using DysonNetwork.Sphere.Auth; -using DysonNetwork.Sphere.Auth.OpenId; using DysonNetwork.Sphere.Chat; using DysonNetwork.Sphere.Chat.Realtime; -using DysonNetwork.Sphere.Connection; -using DysonNetwork.Sphere.Connection.Handlers; -using DysonNetwork.Sphere.Email; using DysonNetwork.Sphere.Localization; -using DysonNetwork.Sphere.Permission; using DysonNetwork.Sphere.Post; using DysonNetwork.Sphere.Publisher; using DysonNetwork.Sphere.Realm; using DysonNetwork.Sphere.Sticker; -using DysonNetwork.Sphere.Storage; -using DysonNetwork.Sphere.Storage.Handlers; -using DysonNetwork.Sphere.Wallet; using Microsoft.AspNetCore.RateLimiting; using Microsoft.OpenApi.Models; using NodaTime; @@ -24,14 +14,16 @@ using NodaTime.Serialization.SystemTextJson; using StackExchange.Redis; using System.Text.Json; using System.Threading.RateLimiting; -using DysonNetwork.Sphere.Auth.OidcProvider.Options; -using DysonNetwork.Sphere.Auth.OidcProvider.Services; -using DysonNetwork.Sphere.Connection.WebReader; +using DysonNetwork.Shared.Auth; +using DysonNetwork.Shared.Cache; +using DysonNetwork.Shared.GeoIp; +using DysonNetwork.Shared.Proto; +using DysonNetwork.Sphere.WebReader; using DysonNetwork.Sphere.Developer; using DysonNetwork.Sphere.Discovery; using DysonNetwork.Sphere.Safety; -using DysonNetwork.Sphere.Wallet.PaymentHandlers; using tusdotnet.Stores; +using PermissionService = DysonNetwork.Sphere.Permission.PermissionService; namespace DysonNetwork.Sphere.Startup; @@ -53,20 +45,6 @@ public static class ServiceCollectionExtensions services.AddHttpClient(); - // Register OIDC services - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddControllers().AddJsonOptions(options => { options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower; @@ -182,16 +160,6 @@ public static class ServiceCollectionExtensions public static IServiceCollection AddAppFlushHandlers(this IServiceCollection services) { services.AddSingleton(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - - // The handlers for websocket - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); return services; } @@ -199,25 +167,9 @@ public static class ServiceCollectionExtensions public static IServiceCollection AddAppBusinessServices(this IServiceCollection services, IConfiguration configuration) { - services.AddScoped(); - services.AddScoped(); services.Configure(configuration.GetSection("GeoIP")); services.AddScoped(); - services.AddScoped(); - services.AddScoped(); services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); @@ -226,20 +178,13 @@ public static class ServiceCollectionExtensions services.AddScoped(); services.AddScoped(); services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); - services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); - services.Configure(configuration.GetSection("OidcProvider")); - services.AddScoped(); - return services; } } \ No newline at end of file diff --git a/DysonNetwork.Sphere/Sticker/Sticker.cs b/DysonNetwork.Sphere/Sticker/Sticker.cs index e109cb5..a6dfc63 100644 --- a/DysonNetwork.Sphere/Sticker/Sticker.cs +++ b/DysonNetwork.Sphere/Sticker/Sticker.cs @@ -1,6 +1,6 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; -using DysonNetwork.Sphere.Storage; +using DysonNetwork.Shared.Data; using Microsoft.EntityFrameworkCore; namespace DysonNetwork.Sphere.Sticker; @@ -19,7 +19,7 @@ public class Sticker : ModelBase, IIdentifiedResource public Guid PackId { get; set; } public StickerPack Pack { get; set; } = null!; - public string ResourceIdentifier => $"sticker/{Id}"; + public string ResourceIdentifier => $"sticker:{Id}"; } [Index(nameof(Prefix), IsUnique = true)] diff --git a/DysonNetwork.Sphere/Sticker/StickerController.cs b/DysonNetwork.Sphere/Sticker/StickerController.cs index 724afb4..7bbb840 100644 --- a/DysonNetwork.Sphere/Sticker/StickerController.cs +++ b/DysonNetwork.Sphere/Sticker/StickerController.cs @@ -1,8 +1,8 @@ using System.ComponentModel.DataAnnotations; +using DysonNetwork.Shared.Data; +using DysonNetwork.Shared.Proto; using DysonNetwork.Sphere.Permission; -using DysonNetwork.Sphere.Post; using DysonNetwork.Sphere.Publisher; -using DysonNetwork.Sphere.Storage; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; @@ -10,10 +10,13 @@ namespace DysonNetwork.Sphere.Sticker; [ApiController] [Route("/api/stickers")] -public class StickerController(AppDatabase db, StickerService st) : ControllerBase +public class StickerController(AppDatabase db, StickerService st, FileService.FileServiceClient files) : ControllerBase { - private async Task _CheckStickerPackPermissions(Guid packId, Account.Account currentUser, - PublisherMemberRole requiredRole) + private async Task _CheckStickerPackPermissions( + Guid packId, + Account currentUser, + PublisherMemberRole requiredRole + ) { var pack = await db.StickerPacks .Include(p => p.Publisher) @@ -22,8 +25,9 @@ public class StickerController(AppDatabase db, StickerService st) : ControllerBa if (pack is null) return NotFound("Sticker pack not found"); + var accountId = Guid.Parse(currentUser.Id); var member = await db.PublisherMembers - .FirstOrDefaultAsync(m => m.AccountId == currentUser.Id && m.PublisherId == pack.PublisherId); + .FirstOrDefaultAsync(m => m.AccountId == accountId && m.PublisherId == pack.PublisherId); if (member is null) return StatusCode(403, "You are not a member of this publisher"); if (member.Role < requiredRole) @@ -78,7 +82,7 @@ public class StickerController(AppDatabase db, StickerService st) : ControllerBa [RequiredPermission("global", "stickers.packs.create")] public async Task> CreateStickerPack([FromBody] StickerPackRequest request) { - if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); if (string.IsNullOrEmpty(request.Name)) return BadRequest("Name is required"); @@ -89,8 +93,9 @@ public class StickerController(AppDatabase db, StickerService st) : ControllerBa if (string.IsNullOrEmpty(publisherName)) return BadRequest("Publisher name is required in X-Pub header"); + var accountId = Guid.Parse(currentUser.Id); var publisher = - await db.Publishers.FirstOrDefaultAsync(p => p.Name == publisherName && p.AccountId == currentUser.Id); + await db.Publishers.FirstOrDefaultAsync(p => p.Name == publisherName && p.AccountId == accountId); if (publisher == null) return BadRequest("Publisher not found"); @@ -110,7 +115,7 @@ public class StickerController(AppDatabase db, StickerService st) : ControllerBa [HttpPatch("{id:guid}")] public async Task> UpdateStickerPack(Guid id, [FromBody] StickerPackRequest request) { - if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); var pack = await db.StickerPacks @@ -119,8 +124,9 @@ public class StickerController(AppDatabase db, StickerService st) : ControllerBa if (pack is null) return NotFound(); + var accountId = Guid.Parse(currentUser.Id); var member = await db.PublisherMembers - .FirstOrDefaultAsync(m => m.AccountId == currentUser.Id && m.PublisherId == pack.PublisherId); + .FirstOrDefaultAsync(m => m.AccountId == accountId && m.PublisherId == pack.PublisherId); if (member is null) return StatusCode(403, "You are not a member of this publisher"); if (member.Role < PublisherMemberRole.Editor) @@ -141,7 +147,7 @@ public class StickerController(AppDatabase db, StickerService st) : ControllerBa [HttpDelete("{id:guid}")] public async Task DeleteStickerPack(Guid id) { - if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); var pack = await db.StickerPacks @@ -150,8 +156,9 @@ public class StickerController(AppDatabase db, StickerService st) : ControllerBa if (pack is null) return NotFound(); + var accountId = Guid.Parse(currentUser.Id); var member = await db.PublisherMembers - .FirstOrDefaultAsync(m => m.AccountId == currentUser.Id && m.PublisherId == pack.PublisherId); + .FirstOrDefaultAsync(m => m.AccountId == accountId && m.PublisherId == pack.PublisherId); if (member is null) return StatusCode(403, "You are not a member of this publisher"); if (member.Role < PublisherMemberRole.Editor) @@ -212,7 +219,7 @@ public class StickerController(AppDatabase db, StickerService st) : ControllerBa [HttpPatch("{packId:guid}/content/{id:guid}")] public async Task UpdateSticker(Guid packId, Guid id, [FromBody] StickerRequest request) { - if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); var permissionCheck = await _CheckStickerPackPermissions(packId, currentUser, PublisherMemberRole.Editor); @@ -231,13 +238,14 @@ public class StickerController(AppDatabase db, StickerService st) : ControllerBa if (request.Slug is not null) sticker.Slug = request.Slug; - CloudFile? image = null; + CloudFileReferenceObject? image = null; if (request.ImageId is not null) { - image = await db.Files.FirstOrDefaultAsync(e => e.Id == request.ImageId); - if (image is null) + var file = await files.GetFileAsync(new GetFileRequest { Id = request.ImageId }); + if (file is null) return BadRequest("Image not found"); sticker.ImageId = request.ImageId; + sticker.Image = CloudFileReferenceObject.FromProtoValue(file); } sticker = await st.UpdateStickerAsync(sticker, image); @@ -247,7 +255,7 @@ public class StickerController(AppDatabase db, StickerService st) : ControllerBa [HttpDelete("{packId:guid}/content/{id:guid}")] public async Task DeleteSticker(Guid packId, Guid id) { - if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); var permissionCheck = await _CheckStickerPackPermissions(packId, currentUser, PublisherMemberRole.Editor); @@ -273,7 +281,7 @@ public class StickerController(AppDatabase db, StickerService st) : ControllerBa [RequiredPermission("global", "stickers.create")] public async Task CreateSticker(Guid packId, [FromBody] StickerRequest request) { - if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); if (string.IsNullOrWhiteSpace(request.Slug)) @@ -295,15 +303,15 @@ public class StickerController(AppDatabase db, StickerService st) : ControllerBa if (stickersCount >= MaxStickersPerPack) return BadRequest($"Sticker pack has reached maximum capacity of {MaxStickersPerPack} stickers."); - var image = await db.Files.FirstOrDefaultAsync(e => e.Id == request.ImageId); - if (image is null) - return BadRequest("Image was not found."); + var file = await files.GetFileAsync(new GetFileRequest { Id = request.ImageId }); + if (file is null) + return BadRequest("Image not found."); var sticker = new Sticker { Slug = request.Slug, - ImageId = image.Id, - Image = image.ToReferenceObject(), + ImageId = file.Id, + Image = CloudFileReferenceObject.FromProtoValue(file), Pack = pack }; diff --git a/DysonNetwork.Sphere/Sticker/StickerService.cs b/DysonNetwork.Sphere/Sticker/StickerService.cs index 217b95c..cd4d01e 100644 --- a/DysonNetwork.Sphere/Sticker/StickerService.cs +++ b/DysonNetwork.Sphere/Sticker/StickerService.cs @@ -1,9 +1,16 @@ -using DysonNetwork.Sphere.Storage; +using DysonNetwork.Shared.Cache; +using DysonNetwork.Shared.Data; +using DysonNetwork.Shared.Proto; using Microsoft.EntityFrameworkCore; namespace DysonNetwork.Sphere.Sticker; -public class StickerService(AppDatabase db, FileService fs, FileReferenceService fileRefService, ICacheService cache) +public class StickerService( + AppDatabase db, + FileService.FileServiceClient files, + FileReferenceService.FileReferenceServiceClient fileRefs, + ICacheService cache +) { public const string StickerFileUsageIdentifier = "sticker"; @@ -26,7 +33,7 @@ public class StickerService(AppDatabase db, FileService fs, FileReferenceService return sticker; } - public async Task UpdateStickerAsync(Sticker sticker, CloudFile? newImage) + public async Task UpdateStickerAsync(Sticker sticker, CloudFileReferenceObject? newImage) { if (newImage is not null) { @@ -104,7 +111,7 @@ public class StickerService(AppDatabase db, FileService fs, FileReferenceService { identifier = identifier.ToLower(); // Try to get from the cache first - var cacheKey = $"stickerlookup:{identifier}"; + var cacheKey = $"sticker:lookup:{identifier}"; var cachedSticker = await cache.GetAsync(cacheKey); if (cachedSticker is not null) return cachedSticker; @@ -128,7 +135,7 @@ public class StickerService(AppDatabase db, FileService fs, FileReferenceService private async Task PurgeStickerCache(Sticker sticker) { // Remove both possible cache entries - await cache.RemoveAsync($"stickerlookup:{sticker.Id}"); - await cache.RemoveAsync($"stickerlookup:{sticker.Pack.Prefix}{sticker.Slug}"); + await cache.RemoveAsync($"sticker:lookup:{sticker.Id}"); + await cache.RemoveAsync($"sticker:lookup:{sticker.Pack.Prefix}{sticker.Slug}"); } } \ No newline at end of file diff --git a/DysonNetwork.Sphere/Storage/CacheService.cs b/DysonNetwork.Sphere/Storage/CacheService.cs deleted file mode 100644 index 7562a0f..0000000 --- a/DysonNetwork.Sphere/Storage/CacheService.cs +++ /dev/null @@ -1,396 +0,0 @@ -using Newtonsoft.Json; -using Newtonsoft.Json.Serialization; -using NodaTime; -using NodaTime.Serialization.JsonNet; -using StackExchange.Redis; - -namespace DysonNetwork.Sphere.Storage; - -/// -/// Represents a distributed lock that can be used to synchronize access across multiple processes -/// -public interface IDistributedLock : IAsyncDisposable -{ - /// - /// The resource identifier this lock is protecting - /// - string Resource { get; } - - /// - /// Unique identifier for this lock instance - /// - string LockId { get; } - - /// - /// Extends the lock's expiration time - /// - Task ExtendAsync(TimeSpan timeSpan); - - /// - /// Releases the lock immediately - /// - Task ReleaseAsync(); -} - -public interface ICacheService -{ - /// - /// Sets a value in the cache with an optional expiration time - /// - Task SetAsync(string key, T value, TimeSpan? expiry = null); - - /// - /// Gets a value from the cache - /// - Task GetAsync(string key); - - /// - /// Get a value from the cache with the found status - /// - Task<(bool found, T? value)> GetAsyncWithStatus(string key); - - /// - /// Removes a specific key from the cache - /// - Task RemoveAsync(string key); - - /// - /// Adds a key to a group for group-based operations - /// - Task AddToGroupAsync(string key, string group); - - /// - /// Removes all keys associated with a specific group - /// - Task RemoveGroupAsync(string group); - - /// - /// Gets all keys belonging to a specific group - /// - Task> GetGroupKeysAsync(string group); - - /// - /// Helper method to set a value in cache and associate it with multiple groups in one operation - /// - /// The type of value being cached - /// Cache key - /// The value to cache - /// Optional collection of group names to associate the key with - /// Optional expiration time for the cached item - /// True if the set operation was successful - Task SetWithGroupsAsync(string key, T value, IEnumerable? groups = null, TimeSpan? expiry = null); - - /// - /// Acquires a distributed lock on the specified resource - /// - /// The resource identifier to lock - /// How long the lock should be held before automatically expiring - /// How long to wait for the lock before giving up - /// How often to retry acquiring the lock during the wait time - /// A distributed lock instance if acquired, null otherwise - Task AcquireLockAsync(string resource, TimeSpan expiry, TimeSpan? waitTime = null, - TimeSpan? retryInterval = null); - - /// - /// Executes an action with a distributed lock, ensuring the lock is properly released afterwards - /// - /// The resource identifier to lock - /// The action to execute while holding the lock - /// How long the lock should be held before automatically expiring - /// How long to wait for the lock before giving up - /// How often to retry acquiring the lock during the wait time - /// True if the lock was acquired and the action was executed, false otherwise - Task ExecuteWithLockAsync(string resource, Func action, TimeSpan expiry, TimeSpan? waitTime = null, - TimeSpan? retryInterval = null); - - /// - /// Executes a function with a distributed lock, ensuring the lock is properly released afterwards - /// - /// The return type of the function - /// The resource identifier to lock - /// The function to execute while holding the lock - /// How long the lock should be held before automatically expiring - /// How long to wait for the lock before giving up - /// How often to retry acquiring the lock during the wait time - /// The result of the function if the lock was acquired, default(T) otherwise - Task<(bool Acquired, T? Result)> ExecuteWithLockAsync(string resource, Func> func, TimeSpan expiry, - TimeSpan? waitTime = null, TimeSpan? retryInterval = null); -} - -public class RedisDistributedLock : IDistributedLock -{ - private readonly IDatabase _database; - private bool _disposed; - - public string Resource { get; } - public string LockId { get; } - - internal RedisDistributedLock(IDatabase database, string resource, string lockId) - { - _database = database; - Resource = resource; - LockId = lockId; - } - - public async Task ExtendAsync(TimeSpan timeSpan) - { - if (_disposed) - throw new ObjectDisposedException(nameof(RedisDistributedLock)); - - var script = @" - if redis.call('get', KEYS[1]) == ARGV[1] then - return redis.call('pexpire', KEYS[1], ARGV[2]) - else - return 0 - end - "; - - var result = await _database.ScriptEvaluateAsync( - script, - [$"{CacheServiceRedis.LockKeyPrefix}{Resource}"], - [LockId, (long)timeSpan.TotalMilliseconds] - ); - - return (long)result! == 1; - } - - public async Task ReleaseAsync() - { - if (_disposed) - return; - - var script = @" - if redis.call('get', KEYS[1]) == ARGV[1] then - return redis.call('del', KEYS[1]) - else - return 0 - end - "; - - await _database.ScriptEvaluateAsync( - script, - [$"{CacheServiceRedis.LockKeyPrefix}{Resource}"], - [LockId] - ); - - _disposed = true; - } - - public async ValueTask DisposeAsync() - { - await ReleaseAsync(); - GC.SuppressFinalize(this); - } -} - -public class CacheServiceRedis : ICacheService -{ - private readonly IDatabase _database; - private readonly JsonSerializerSettings _serializerSettings; - - // Global prefix for all cache keys - public const string GlobalKeyPrefix = "dyson:"; - - // Using prefixes for different types of keys - public const string GroupKeyPrefix = GlobalKeyPrefix + "cg:"; - public const string LockKeyPrefix = GlobalKeyPrefix + "lock:"; - - public CacheServiceRedis(IConnectionMultiplexer redis) - { - var rds = redis ?? throw new ArgumentNullException(nameof(redis)); - _database = rds.GetDatabase(); - - // Configure Newtonsoft.Json with proper NodaTime serialization - _serializerSettings = new JsonSerializerSettings - { - ContractResolver = new CamelCasePropertyNamesContractResolver(), - PreserveReferencesHandling = PreserveReferencesHandling.Objects, - NullValueHandling = NullValueHandling.Include, - DateParseHandling = DateParseHandling.None - }; - - // Configure NodaTime serializers - _serializerSettings.ConfigureForNodaTime(DateTimeZoneProviders.Tzdb); - } - - public async Task SetAsync(string key, T value, TimeSpan? expiry = null) - { - key = $"{GlobalKeyPrefix}{key}"; - if (string.IsNullOrEmpty(key)) - throw new ArgumentException("Key cannot be null or empty", nameof(key)); - - var serializedValue = JsonConvert.SerializeObject(value, _serializerSettings); - return await _database.StringSetAsync(key, serializedValue, expiry); - } - - public async Task GetAsync(string key) - { - key = $"{GlobalKeyPrefix}{key}"; - if (string.IsNullOrEmpty(key)) - throw new ArgumentException("Key cannot be null or empty", nameof(key)); - - var value = await _database.StringGetAsync(key); - - if (value.IsNullOrEmpty) - return default; - - // For NodaTime serialization, use the configured serializer settings - return JsonConvert.DeserializeObject(value!, _serializerSettings); - } - - public async Task<(bool found, T? value)> GetAsyncWithStatus(string key) - { - key = $"{GlobalKeyPrefix}{key}"; - if (string.IsNullOrEmpty(key)) - throw new ArgumentException("Key cannot be null or empty", nameof(key)); - - var value = await _database.StringGetAsync(key); - - if (value.IsNullOrEmpty) - return (false, default); - - // For NodaTime serialization, use the configured serializer settings - return (true, JsonConvert.DeserializeObject(value!, _serializerSettings)); - } - - public async Task RemoveAsync(string key) - { - key = $"{GlobalKeyPrefix}{key}"; - if (string.IsNullOrEmpty(key)) - throw new ArgumentException("Key cannot be null or empty", nameof(key)); - - // Before removing the key, find all groups it belongs to and remove it from them - var script = @" - local groups = redis.call('KEYS', ARGV[1]) - for _, group in ipairs(groups) do - redis.call('SREM', group, ARGV[2]) - end - return redis.call('DEL', ARGV[2]) - "; - - var result = await _database.ScriptEvaluateAsync( - script, - values: [$"{GroupKeyPrefix}*", key] - ); - - return (long)result! > 0; - } - - public async Task AddToGroupAsync(string key, string group) - { - if (string.IsNullOrEmpty(key)) - throw new ArgumentException(@"Key cannot be null or empty.", nameof(key)); - - if (string.IsNullOrEmpty(group)) - throw new ArgumentException(@"Group cannot be null or empty.", nameof(group)); - - var groupKey = $"{GroupKeyPrefix}{group}"; - key = $"{GlobalKeyPrefix}{key}"; - await _database.SetAddAsync(groupKey, key); - } - - public async Task RemoveGroupAsync(string group) - { - if (string.IsNullOrEmpty(group)) - throw new ArgumentException(@"Group cannot be null or empty.", nameof(group)); - - var groupKey = $"{GroupKeyPrefix}{group}"; - - // Get all keys in the group - var keys = await _database.SetMembersAsync(groupKey); - - if (keys.Length > 0) - { - // Delete all the keys - var keysTasks = keys.Select(key => _database.KeyDeleteAsync(key.ToString())); - await Task.WhenAll(keysTasks); - } - - // Delete the group itself - await _database.KeyDeleteAsync(groupKey); - } - - public async Task> GetGroupKeysAsync(string group) - { - if (string.IsNullOrEmpty(group)) - throw new ArgumentException(@"Group cannot be null or empty.", nameof(group)); - - var groupKey = $"{GroupKeyPrefix}{group}"; - var members = await _database.SetMembersAsync(groupKey); - - return members.Select(m => m.ToString()); - } - - public async Task SetWithGroupsAsync(string key, T value, IEnumerable? groups = null, - TimeSpan? expiry = null) - { - // First, set the value in the cache - var setResult = await SetAsync(key, value, expiry); - - // If successful and there are groups to associate, add the key to each group - if (!setResult || groups == null) return setResult; - var groupsArray = groups.Where(g => !string.IsNullOrEmpty(g)).ToArray(); - if (groupsArray.Length <= 0) return setResult; - var tasks = groupsArray.Select(group => AddToGroupAsync(key, group)); - await Task.WhenAll(tasks); - - return setResult; - } - - public async Task AcquireLockAsync(string resource, TimeSpan expiry, TimeSpan? waitTime = null, - TimeSpan? retryInterval = null) - { - if (string.IsNullOrEmpty(resource)) - throw new ArgumentException("Resource cannot be null or empty", nameof(resource)); - - var lockKey = $"{LockKeyPrefix}{resource}"; - var lockId = Guid.NewGuid().ToString("N"); - var waitTimeSpan = waitTime ?? TimeSpan.Zero; - var retryIntervalSpan = retryInterval ?? TimeSpan.FromMilliseconds(100); - - var startTime = DateTime.UtcNow; - var acquired = false; - - // Try to acquire the lock, retry until waitTime is exceeded - while (!acquired && (DateTime.UtcNow - startTime) < waitTimeSpan) - { - acquired = await _database.StringSetAsync(lockKey, lockId, expiry, When.NotExists); - - if (!acquired) - { - await Task.Delay(retryIntervalSpan); - } - } - - if (!acquired) - { - return null; // Could not acquire the lock within the wait time - } - - return new RedisDistributedLock(_database, resource, lockId); - } - - public async Task ExecuteWithLockAsync(string resource, Func action, TimeSpan expiry, - TimeSpan? waitTime = null, TimeSpan? retryInterval = null) - { - await using var lockObj = await AcquireLockAsync(resource, expiry, waitTime, retryInterval); - - if (lockObj == null) - return false; // Could not acquire the lock - - await action(); - return true; - } - - public async Task<(bool Acquired, T? Result)> ExecuteWithLockAsync(string resource, Func> func, - TimeSpan expiry, TimeSpan? waitTime = null, TimeSpan? retryInterval = null) - { - await using var lockObj = await AcquireLockAsync(resource, expiry, waitTime, retryInterval); - - if (lockObj == null) - return (false, default); // Could not acquire the lock - - var result = await func(); - return (true, result); - } -} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Storage/CloudFile.cs b/DysonNetwork.Sphere/Storage/CloudFile.cs deleted file mode 100644 index 1cc1838..0000000 --- a/DysonNetwork.Sphere/Storage/CloudFile.cs +++ /dev/null @@ -1,130 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; -using System.Text.Json.Serialization; -using NodaTime; - -namespace DysonNetwork.Sphere.Storage; - -public class RemoteStorageConfig -{ - public string Id { get; set; } = string.Empty; - public string Label { get; set; } = string.Empty; - public string Region { get; set; } = string.Empty; - public string Bucket { get; set; } = string.Empty; - public string Endpoint { get; set; } = string.Empty; - public string SecretId { get; set; } = string.Empty; - public string SecretKey { get; set; } = string.Empty; - public bool EnableSigned { get; set; } - public bool EnableSsl { get; set; } - public string? ImageProxy { get; set; } - public string? AccessProxy { get; set; } -} - -/// -/// The class that used in jsonb columns which referenced the cloud file. -/// The aim of this class is to store some properties that won't change to a file to reduce the database load. -/// -public class CloudFileReferenceObject : ModelBase, ICloudFile -{ - public string Id { get; set; } = null!; - public string Name { get; set; } = string.Empty; - public Dictionary? FileMeta { get; set; } = null!; - public Dictionary? UserMeta { get; set; } = null!; - public string? MimeType { get; set; } - public string? Hash { get; set; } - public long Size { get; set; } - public bool HasCompression { get; set; } = false; -} - -public class CloudFile : ModelBase, ICloudFile, IIdentifiedResource -{ - /// The id generated by TuS, basically just UUID remove the dash lines - [MaxLength(32)] - public string Id { get; set; } = Guid.NewGuid().ToString(); - - [MaxLength(1024)] public string Name { get; set; } = string.Empty; - [MaxLength(4096)] public string? Description { get; set; } - [Column(TypeName = "jsonb")] public Dictionary? FileMeta { get; set; } = null!; - [Column(TypeName = "jsonb")] public Dictionary? UserMeta { get; set; } = null!; - [Column(TypeName = "jsonb")] public List? SensitiveMarks { get; set; } = []; - [MaxLength(256)] public string? MimeType { get; set; } - [MaxLength(256)] public string? Hash { get; set; } - public long Size { get; set; } - public Instant? UploadedAt { get; set; } - [MaxLength(128)] public string? UploadedTo { get; set; } - public bool HasCompression { get; set; } = false; - - /// - /// The field is set to true if the recycling job plans to delete the file. - /// Due to the unstable of the recycling job, this doesn't really delete the file until a human verifies it. - /// - public bool IsMarkedRecycle { get; set; } = false; - - /// The object name which stored remotely, - /// multiple cloud file may have same storage id to indicate they are the same file - /// - /// If the storage id was null and the uploaded at is not null, means it is an embedding file, - /// The embedding file means the file is store on another site, - /// or it is a webpage (based on mimetype) - [MaxLength(32)] - public string? StorageId { get; set; } - - /// This field should be null when the storage id is filled - /// Indicates the off-site accessible url of the file - [MaxLength(4096)] - public string? StorageUrl { get; set; } - - [JsonIgnore] public Account.Account Account { get; set; } = null!; - public Guid AccountId { get; set; } - - public CloudFileReferenceObject ToReferenceObject() - { - return new CloudFileReferenceObject - { - CreatedAt = CreatedAt, - UpdatedAt = UpdatedAt, - DeletedAt = DeletedAt, - Id = Id, - Name = Name, - FileMeta = FileMeta, - UserMeta = UserMeta, - MimeType = MimeType, - Hash = Hash, - Size = Size, - HasCompression = HasCompression - }; - } - - public string ResourceIdentifier => $"file/{Id}"; -} - -public enum ContentSensitiveMark -{ - Language, - SexualContent, - Violence, - Profanity, - HateSpeech, - Racism, - AdultContent, - DrugAbuse, - AlcoholAbuse, - Gambling, - SelfHarm, - ChildAbuse, - Other -} - -public class CloudFileReference : ModelBase -{ - public Guid Id { get; set; } = Guid.NewGuid(); - [MaxLength(32)] public string FileId { get; set; } = null!; - public CloudFile File { get; set; } = null!; - [MaxLength(1024)] public string Usage { get; set; } = null!; - [MaxLength(1024)] public string ResourceId { get; set; } = null!; - - /// - /// Optional expiration date for the file reference - /// - public Instant? ExpiredAt { get; set; } -} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Storage/CloudFileUnusedRecyclingJob.cs b/DysonNetwork.Sphere/Storage/CloudFileUnusedRecyclingJob.cs deleted file mode 100644 index 6b97a06..0000000 --- a/DysonNetwork.Sphere/Storage/CloudFileUnusedRecyclingJob.cs +++ /dev/null @@ -1,93 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using NodaTime; -using Quartz; - -namespace DysonNetwork.Sphere.Storage; - -public class CloudFileUnusedRecyclingJob( - AppDatabase db, - FileReferenceService fileRefService, - ILogger logger -) - : IJob -{ - public async Task Execute(IJobExecutionContext context) - { - logger.LogInformation("Marking unused cloud files..."); - - var now = SystemClock.Instance.GetCurrentInstant(); - const int batchSize = 1000; // Process larger batches for efficiency - var processedCount = 0; - var markedCount = 0; - var totalFiles = await db.Files.Where(f => !f.IsMarkedRecycle).CountAsync(); - - logger.LogInformation("Found {TotalFiles} files to check for unused status", totalFiles); - - // Define a timestamp to limit the age of files we're processing in this run - // This spreads the processing across multiple job runs for very large databases - var ageThreshold = now - Duration.FromDays(30); // Process files up to 90 days old in this run - - // Instead of loading all files at once, use pagination - var hasMoreFiles = true; - string? lastProcessedId = null; - - while (hasMoreFiles) - { - // Query for the next batch of files using keyset pagination - var filesQuery = db.Files - .Where(f => !f.IsMarkedRecycle) - .Where(f => f.CreatedAt <= ageThreshold); // Only process older files first - - if (lastProcessedId != null) - { - filesQuery = filesQuery.Where(f => string.Compare(f.Id, lastProcessedId) > 0); - } - - var fileBatch = await filesQuery - .OrderBy(f => f.Id) // Ensure consistent ordering for pagination - .Take(batchSize) - .Select(f => f.Id) - .ToListAsync(); - - if (fileBatch.Count == 0) - { - hasMoreFiles = false; - continue; - } - - processedCount += fileBatch.Count; - lastProcessedId = fileBatch.Last(); - - // Get all relevant file references for this batch - var fileReferences = await fileRefService.GetReferencesAsync(fileBatch); - - // Filter to find files that have no references or all expired references - var filesToMark = fileBatch.Where(fileId => - !fileReferences.TryGetValue(fileId, out var references) || - references.Count == 0 || - references.All(r => r.ExpiredAt.HasValue && r.ExpiredAt.Value <= now) - ).ToList(); - - if (filesToMark.Count > 0) - { - // Use a bulk update for better performance - mark all qualifying files at once - var updateCount = await db.Files - .Where(f => filesToMark.Contains(f.Id)) - .ExecuteUpdateAsync(setter => setter - .SetProperty(f => f.IsMarkedRecycle, true)); - - markedCount += updateCount; - } - - // Log progress periodically - if (processedCount % 10000 == 0 || !hasMoreFiles) - { - logger.LogInformation( - "Progress: processed {ProcessedCount}/{TotalFiles} files, marked {MarkedCount} for recycling", - processedCount, totalFiles, markedCount); - } - } - - logger.LogInformation("Completed marking {MarkedCount} files for recycling", markedCount); - } -} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Storage/FileController.cs b/DysonNetwork.Sphere/Storage/FileController.cs deleted file mode 100644 index d13f531..0000000 --- a/DysonNetwork.Sphere/Storage/FileController.cs +++ /dev/null @@ -1,154 +0,0 @@ -using DysonNetwork.Sphere.Permission; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; -using Minio.DataModel.Args; - -namespace DysonNetwork.Sphere.Storage; - -[ApiController] -[Route("/api/files")] -public class FileController( - AppDatabase db, - FileService fs, - IConfiguration configuration, - IWebHostEnvironment env, - FileReferenceMigrationService rms -) : ControllerBase -{ - [HttpGet("{id}")] - public async Task OpenFile( - string id, - [FromQuery] bool download = false, - [FromQuery] bool original = false, - [FromQuery] string? overrideMimeType = null - ) - { - // Support the file extension for client side data recognize - string? fileExtension = null; - if (id.Contains('.')) - { - var splitId = id.Split('.'); - id = splitId.First(); - fileExtension = splitId.Last(); - } - - var file = await fs.GetFileAsync(id); - if (file is null) return NotFound(); - - if (!string.IsNullOrWhiteSpace(file.StorageUrl)) return Redirect(file.StorageUrl); - - if (file.UploadedTo is null) - { - var tusStorePath = configuration.GetValue("Tus:StorePath")!; - var filePath = Path.Combine(env.ContentRootPath, tusStorePath, file.Id); - if (!System.IO.File.Exists(filePath)) return new NotFoundResult(); - return PhysicalFile(filePath, file.MimeType ?? "application/octet-stream", file.Name); - } - - var dest = fs.GetRemoteStorageConfig(file.UploadedTo); - var fileName = string.IsNullOrWhiteSpace(file.StorageId) ? file.Id : file.StorageId; - - if (!original && file.HasCompression) - fileName += ".compressed"; - - if (dest.ImageProxy is not null && (file.MimeType?.StartsWith("image/") ?? false)) - { - var proxyUrl = dest.ImageProxy; - var baseUri = new Uri(proxyUrl.EndsWith('/') ? proxyUrl : $"{proxyUrl}/"); - var fullUri = new Uri(baseUri, fileName); - return Redirect(fullUri.ToString()); - } - - if (dest.AccessProxy is not null) - { - var proxyUrl = dest.AccessProxy; - var baseUri = new Uri(proxyUrl.EndsWith('/') ? proxyUrl : $"{proxyUrl}/"); - var fullUri = new Uri(baseUri, fileName); - return Redirect(fullUri.ToString()); - } - - if (dest.EnableSigned) - { - var client = fs.CreateMinioClient(dest); - if (client is null) - return BadRequest( - "Failed to configure client for remote destination, file got an invalid storage remote."); - - var headers = new Dictionary(); - if (fileExtension is not null) - { - if (MimeTypes.TryGetMimeType(fileExtension, out var mimeType)) - headers.Add("Response-Content-Type", mimeType); - } - else if (overrideMimeType is not null) - { - headers.Add("Response-Content-Type", overrideMimeType); - } - else if (file.MimeType is not null && !file.MimeType!.EndsWith("unknown")) - { - headers.Add("Response-Content-Type", file.MimeType); - } - - if (download) - { - headers.Add("Response-Content-Disposition", $"attachment; filename=\"{file.Name}\""); - } - - var bucket = dest.Bucket; - var openUrl = await client.PresignedGetObjectAsync( - new PresignedGetObjectArgs() - .WithBucket(bucket) - .WithObject(fileName) - .WithExpiry(3600) - .WithHeaders(headers) - ); - - return Redirect(openUrl); - } - - // Fallback redirect to the S3 endpoint (public read) - var protocol = dest.EnableSsl ? "https" : "http"; - // Use the path bucket lookup mode - return Redirect($"{protocol}://{dest.Endpoint}/{dest.Bucket}/{fileName}"); - } - - [HttpGet("{id}/info")] - public async Task> GetFileInfo(string id) - { - var file = await db.Files.FindAsync(id); - if (file is null) return NotFound(); - - return file; - } - - [Authorize] - [HttpDelete("{id}")] - public async Task DeleteFile(string id) - { - if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); - var userId = currentUser.Id; - - var file = await db.Files - .Where(e => e.Id == id) - .Where(e => e.Account.Id == userId) - .FirstOrDefaultAsync(); - if (file is null) return NotFound(); - - await fs.DeleteFileAsync(file); - - db.Files.Remove(file); - await db.SaveChangesAsync(); - - return NoContent(); - } - - [HttpPost("/maintenance/migrateReferences")] - [Authorize] - [RequiredPermission("maintenance", "files.references")] - public async Task MigrateFileReferences() - { - await rms.ScanAndMigrateReferences(); - return Ok(); - } -} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Storage/FileExpirationJob.cs b/DysonNetwork.Sphere/Storage/FileExpirationJob.cs deleted file mode 100644 index 50ccc17..0000000 --- a/DysonNetwork.Sphere/Storage/FileExpirationJob.cs +++ /dev/null @@ -1,66 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using NodaTime; -using Quartz; - -namespace DysonNetwork.Sphere.Storage; - -/// -/// Job responsible for cleaning up expired file references -/// -public class FileExpirationJob(AppDatabase db, FileService fileService, ILogger logger) : IJob -{ - public async Task Execute(IJobExecutionContext context) - { - var now = SystemClock.Instance.GetCurrentInstant(); - logger.LogInformation("Running file reference expiration job at {now}", now); - - // Find all expired references - var expiredReferences = await db.FileReferences - .Where(r => r.ExpiredAt < now && r.ExpiredAt != null) - .ToListAsync(); - - if (!expiredReferences.Any()) - { - logger.LogInformation("No expired file references found"); - return; - } - - logger.LogInformation("Found {count} expired file references", expiredReferences.Count); - - // Get unique file IDs - var fileIds = expiredReferences.Select(r => r.FileId).Distinct().ToList(); - var filesAndReferenceCount = new Dictionary(); - - // Delete expired references - db.FileReferences.RemoveRange(expiredReferences); - await db.SaveChangesAsync(); - - // Check remaining references for each file - foreach (var fileId in fileIds) - { - var remainingReferences = await db.FileReferences - .Where(r => r.FileId == fileId) - .CountAsync(); - - filesAndReferenceCount[fileId] = remainingReferences; - - // If no references remain, delete the file - if (remainingReferences == 0) - { - var file = await db.Files.FirstOrDefaultAsync(f => f.Id == fileId); - if (file != null) - { - logger.LogInformation("Deleting file {fileId} as all references have expired", fileId); - await fileService.DeleteFileAsync(file); - } - } - else - { - // Just purge the cache - await fileService._PurgeCacheAsync(fileId); - } - } - - logger.LogInformation("Completed file reference expiration job"); - } -} diff --git a/DysonNetwork.Sphere/Storage/FileReferenceService.cs b/DysonNetwork.Sphere/Storage/FileReferenceService.cs deleted file mode 100644 index 6c92682..0000000 --- a/DysonNetwork.Sphere/Storage/FileReferenceService.cs +++ /dev/null @@ -1,433 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using NodaTime; - -namespace DysonNetwork.Sphere.Storage; - -public class FileReferenceService(AppDatabase db, FileService fileService, ICacheService cache) -{ - private const string CacheKeyPrefix = "fileref:"; - private static readonly TimeSpan CacheDuration = TimeSpan.FromMinutes(15); - - /// - /// Creates a new reference to a file for a specific resource - /// - /// The ID of the file to reference - /// The usage context (e.g., "avatar", "post-attachment") - /// The ID of the resource using the file - /// Optional expiration time for the file - /// Optional duration after which the file expires (alternative to expiredAt) - /// The created file reference - public async Task CreateReferenceAsync( - string fileId, - string usage, - string resourceId, - Instant? expiredAt = null, - Duration? duration = null) - { - // Calculate expiration time if needed - var finalExpiration = expiredAt; - if (duration.HasValue) - finalExpiration = SystemClock.Instance.GetCurrentInstant() + duration.Value; - - var reference = new CloudFileReference - { - FileId = fileId, - Usage = usage, - ResourceId = resourceId, - ExpiredAt = finalExpiration - }; - - db.FileReferences.Add(reference); - - await db.SaveChangesAsync(); - await fileService._PurgeCacheAsync(fileId); - - return reference; - } - - /// - /// Gets all references to a file - /// - /// The ID of the file - /// A list of all references to the file - public async Task> GetReferencesAsync(string fileId) - { - var cacheKey = $"{CacheKeyPrefix}list:{fileId}"; - - var cachedReferences = await cache.GetAsync>(cacheKey); - if (cachedReferences is not null) - return cachedReferences; - - var references = await db.FileReferences - .Where(r => r.FileId == fileId) - .ToListAsync(); - - await cache.SetAsync(cacheKey, references, CacheDuration); - - return references; - } - - public async Task>> GetReferencesAsync(IEnumerable fileId) - { - var references = await db.FileReferences - .Where(r => fileId.Contains(r.FileId)) - .GroupBy(r => r.FileId) - .ToDictionaryAsync(r => r.Key, r => r.ToList()); - return references; - } - - /// - /// Gets the number of references to a file - /// - /// The ID of the file - /// The number of references to the file - public async Task GetReferenceCountAsync(string fileId) - { - var cacheKey = $"{CacheKeyPrefix}count:{fileId}"; - - var cachedCount = await cache.GetAsync(cacheKey); - if (cachedCount.HasValue) - return cachedCount.Value; - - var count = await db.FileReferences - .Where(r => r.FileId == fileId) - .CountAsync(); - - await cache.SetAsync(cacheKey, count, CacheDuration); - - return count; - } - - /// - /// Gets all references for a specific resource - /// - /// The ID of the resource - /// A list of file references associated with the resource - public async Task> GetResourceReferencesAsync(string resourceId) - { - var cacheKey = $"{CacheKeyPrefix}resource:{resourceId}"; - - var cachedReferences = await cache.GetAsync>(cacheKey); - if (cachedReferences is not null) - return cachedReferences; - - var references = await db.FileReferences - .Where(r => r.ResourceId == resourceId) - .ToListAsync(); - - await cache.SetAsync(cacheKey, references, CacheDuration); - - return references; - } - - /// - /// Gets all file references for a specific usage context - /// - /// The usage context - /// A list of file references with the specified usage - public async Task> GetUsageReferencesAsync(string usage) - { - return await db.FileReferences - .Where(r => r.Usage == usage) - .ToListAsync(); - } - - /// - /// Deletes references for a specific resource - /// - /// The ID of the resource - /// The number of deleted references - public async Task DeleteResourceReferencesAsync(string resourceId) - { - var references = await db.FileReferences - .Where(r => r.ResourceId == resourceId) - .ToListAsync(); - - var fileIds = references.Select(r => r.FileId).Distinct().ToList(); - - db.FileReferences.RemoveRange(references); - var deletedCount = await db.SaveChangesAsync(); - - // Purge caches - var tasks = fileIds.Select(fileService._PurgeCacheAsync).ToList(); - tasks.Add(PurgeCacheForResourceAsync(resourceId)); - await Task.WhenAll(tasks); - - return deletedCount; - } - - /// - /// Deletes references for a specific resource and usage - /// - /// The ID of the resource - /// The usage context - /// The number of deleted references - public async Task DeleteResourceReferencesAsync(string resourceId, string usage) - { - var references = await db.FileReferences - .Where(r => r.ResourceId == resourceId && r.Usage == usage) - .ToListAsync(); - - if (!references.Any()) - { - return 0; - } - - var fileIds = references.Select(r => r.FileId).Distinct().ToList(); - - db.FileReferences.RemoveRange(references); - var deletedCount = await db.SaveChangesAsync(); - - // Purge caches - var tasks = fileIds.Select(fileService._PurgeCacheAsync).ToList(); - tasks.Add(PurgeCacheForResourceAsync(resourceId)); - await Task.WhenAll(tasks); - - return deletedCount; - } - - /// - /// Deletes a specific file reference - /// - /// The ID of the reference to delete - /// True if the reference was deleted, false otherwise - public async Task DeleteReferenceAsync(Guid referenceId) - { - var reference = await db.FileReferences - .FirstOrDefaultAsync(r => r.Id == referenceId); - - if (reference == null) - return false; - - db.FileReferences.Remove(reference); - await db.SaveChangesAsync(); - - // Purge caches - await fileService._PurgeCacheAsync(reference.FileId); - await PurgeCacheForResourceAsync(reference.ResourceId); - await PurgeCacheForFileAsync(reference.FileId); - - return true; - } - - /// - /// Updates the files referenced by a resource - /// - /// The ID of the resource - /// The new list of file IDs - /// The usage context - /// Optional expiration time for newly added files - /// Optional duration after which newly added files expire - /// A list of the updated file references - public async Task> UpdateResourceFilesAsync( - string resourceId, - IEnumerable? newFileIds, - string usage, - Instant? expiredAt = null, - Duration? duration = null) - { - if (newFileIds == null) - return new List(); - - var existingReferences = await db.FileReferences - .Where(r => r.ResourceId == resourceId && r.Usage == usage) - .ToListAsync(); - - var existingFileIds = existingReferences.Select(r => r.FileId).ToHashSet(); - var newFileIdsList = newFileIds.ToList(); - var newFileIdsSet = newFileIdsList.ToHashSet(); - - // Files to remove - var toRemove = existingReferences - .Where(r => !newFileIdsSet.Contains(r.FileId)) - .ToList(); - - // Files to add - var toAdd = newFileIdsList - .Where(id => !existingFileIds.Contains(id)) - .Select(id => new CloudFileReference - { - FileId = id, - Usage = usage, - ResourceId = resourceId - }) - .ToList(); - - // Apply changes - if (toRemove.Any()) - db.FileReferences.RemoveRange(toRemove); - - if (toAdd.Any()) - db.FileReferences.AddRange(toAdd); - - await db.SaveChangesAsync(); - - // Update expiration for newly added references if specified - if ((expiredAt.HasValue || duration.HasValue) && toAdd.Any()) - { - var finalExpiration = expiredAt; - if (duration.HasValue) - { - finalExpiration = SystemClock.Instance.GetCurrentInstant() + duration.Value; - } - - // Update newly added references with the expiration time - var referenceIds = await db.FileReferences - .Where(r => toAdd.Select(a => a.FileId).Contains(r.FileId) && - r.ResourceId == resourceId && - r.Usage == usage) - .Select(r => r.Id) - .ToListAsync(); - - await db.FileReferences - .Where(r => referenceIds.Contains(r.Id)) - .ExecuteUpdateAsync(setter => setter.SetProperty( - r => r.ExpiredAt, - _ => finalExpiration - )); - } - - // Purge caches - var allFileIds = existingFileIds.Union(newFileIdsSet).ToList(); - var tasks = allFileIds.Select(fileService._PurgeCacheAsync).ToList(); - tasks.Add(PurgeCacheForResourceAsync(resourceId)); - await Task.WhenAll(tasks); - - // Return updated references - return await db.FileReferences - .Where(r => r.ResourceId == resourceId && r.Usage == usage) - .ToListAsync(); - } - - /// - /// Gets all files referenced by a resource - /// - /// The ID of the resource - /// Optional filter by usage context - /// A list of files referenced by the resource - public async Task> GetResourceFilesAsync(string resourceId, string? usage = null) - { - var query = db.FileReferences.Where(r => r.ResourceId == resourceId); - - if (usage != null) - query = query.Where(r => r.Usage == usage); - - var references = await query.ToListAsync(); - var fileIds = references.Select(r => r.FileId).ToList(); - - return await db.Files - .Where(f => fileIds.Contains(f.Id)) - .ToListAsync(); - } - - /// - /// Purges all caches related to a resource - /// - private async Task PurgeCacheForResourceAsync(string resourceId) - { - var cacheKey = $"{CacheKeyPrefix}resource:{resourceId}"; - await cache.RemoveAsync(cacheKey); - } - - /// - /// Purges all caches related to a file - /// - private async Task PurgeCacheForFileAsync(string fileId) - { - var cacheKeys = new[] - { - $"{CacheKeyPrefix}list:{fileId}", - $"{CacheKeyPrefix}count:{fileId}" - }; - - var tasks = cacheKeys.Select(cache.RemoveAsync); - await Task.WhenAll(tasks); - } - - /// - /// Updates the expiration time for a file reference - /// - /// The ID of the reference - /// The new expiration time, or null to remove expiration - /// True if the reference was found and updated, false otherwise - public async Task SetReferenceExpirationAsync(Guid referenceId, Instant? expiredAt) - { - var reference = await db.FileReferences - .FirstOrDefaultAsync(r => r.Id == referenceId); - - if (reference == null) - return false; - - reference.ExpiredAt = expiredAt; - await db.SaveChangesAsync(); - - await PurgeCacheForFileAsync(reference.FileId); - await PurgeCacheForResourceAsync(reference.ResourceId); - - return true; - } - - /// - /// Updates the expiration time for all references to a file - /// - /// The ID of the file - /// The new expiration time, or null to remove expiration - /// The number of references updated - public async Task SetFileReferencesExpirationAsync(string fileId, Instant? expiredAt) - { - var rowsAffected = await db.FileReferences - .Where(r => r.FileId == fileId) - .ExecuteUpdateAsync(setter => setter.SetProperty( - r => r.ExpiredAt, - _ => expiredAt - )); - - if (rowsAffected > 0) - { - await fileService._PurgeCacheAsync(fileId); - await PurgeCacheForFileAsync(fileId); - } - - return rowsAffected; - } - - /// - /// Get all file references for a specific resource and usage type - /// - /// The resource ID - /// The usage type - /// List of file references - public async Task> GetResourceReferencesAsync(string resourceId, string usageType) - { - return await db.FileReferences - .Where(r => r.ResourceId == resourceId && r.Usage == usageType) - .ToListAsync(); - } - - /// - /// Check if a file has any references - /// - /// The file ID to check - /// True if the file has references, false otherwise - public async Task HasFileReferencesAsync(string fileId) - { - return await db.FileReferences.AnyAsync(r => r.FileId == fileId); - } - - /// - /// Updates the expiration time for a file reference using a duration from now - /// - /// The ID of the reference - /// The duration after which the reference expires, or null to remove expiration - /// True if the reference was found and updated, false otherwise - public async Task SetReferenceExpirationDurationAsync(Guid referenceId, Duration? duration) - { - Instant? expiredAt = null; - if (duration.HasValue) - { - expiredAt = SystemClock.Instance.GetCurrentInstant() + duration.Value; - } - - return await SetReferenceExpirationAsync(referenceId, expiredAt); - } -} diff --git a/DysonNetwork.Sphere/Storage/FileService.ReferenceMigration.cs b/DysonNetwork.Sphere/Storage/FileService.ReferenceMigration.cs deleted file mode 100644 index cec315a..0000000 --- a/DysonNetwork.Sphere/Storage/FileService.ReferenceMigration.cs +++ /dev/null @@ -1,339 +0,0 @@ -using EFCore.BulkExtensions; -using Microsoft.EntityFrameworkCore; -using NodaTime; - -namespace DysonNetwork.Sphere.Storage; - -public class FileReferenceMigrationService(AppDatabase db) -{ - public async Task ScanAndMigrateReferences() - { - // Scan Posts for file references - await ScanPosts(); - - // Scan Messages for file references - await ScanMessages(); - - // Scan Profiles for file references - await ScanProfiles(); - - // Scan Chat entities for file references - await ScanChatRooms(); - - // Scan Realms for file references - await ScanRealms(); - - // Scan Publishers for file references - await ScanPublishers(); - - // Scan Stickers for file references - await ScanStickers(); - } - - private async Task ScanPosts() - { - var posts = await db.Posts - .Include(p => p.OutdatedAttachments) - .Where(p => p.OutdatedAttachments.Any()) - .ToListAsync(); - - foreach (var post in posts) - { - var updatedAttachments = new List(); - - foreach (var attachment in post.OutdatedAttachments) - { - var file = await db.Files.FirstOrDefaultAsync(f => f.Id == attachment.Id); - if (file != null) - { - // Create a reference for the file - var reference = new CloudFileReference - { - FileId = file.Id, - File = file, - Usage = "post", - ResourceId = post.ResourceIdentifier - }; - - await db.FileReferences.AddAsync(reference); - updatedAttachments.Add(file.ToReferenceObject()); - } - else - { - // Keep the existing reference object if file not found - updatedAttachments.Add(attachment.ToReferenceObject()); - } - } - - post.Attachments = updatedAttachments; - db.Posts.Update(post); - } - - await db.SaveChangesAsync(); - } - - private async Task ScanMessages() - { - var messages = await db.ChatMessages - .Include(m => m.OutdatedAttachments) - .Where(m => m.OutdatedAttachments.Any()) - .ToListAsync(); - - var fileReferences = messages.SelectMany(message => message.OutdatedAttachments.Select(attachment => - new CloudFileReference - { - FileId = attachment.Id, - File = attachment, - Usage = "chat", - ResourceId = message.ResourceIdentifier, - CreatedAt = SystemClock.Instance.GetCurrentInstant(), - UpdatedAt = SystemClock.Instance.GetCurrentInstant() - }) - ).ToList(); - - foreach (var message in messages) - { - message.Attachments = message.OutdatedAttachments.Select(a => a.ToReferenceObject()).ToList(); - db.ChatMessages.Update(message); - } - - await db.BulkInsertAsync(fileReferences); - await db.SaveChangesAsync(); - } - - private async Task ScanProfiles() - { - var profiles = await db.AccountProfiles - .Where(p => p.PictureId != null || p.BackgroundId != null) - .ToListAsync(); - - foreach (var profile in profiles) - { - if (profile is { PictureId: not null, Picture: null }) - { - var avatarFile = await db.Files.FirstOrDefaultAsync(f => f.Id == profile.PictureId); - if (avatarFile != null) - { - // Create a reference for the avatar file - var reference = new CloudFileReference - { - FileId = avatarFile.Id, - File = avatarFile, - Usage = "profile.picture", - ResourceId = profile.Id.ToString() - }; - - await db.FileReferences.AddAsync(reference); - profile.Picture = avatarFile.ToReferenceObject(); - db.AccountProfiles.Update(profile); - } - } - - // Also check for the banner if it exists - if (profile is not { BackgroundId: not null, Background: null }) continue; - var bannerFile = await db.Files.FirstOrDefaultAsync(f => f.Id == profile.BackgroundId); - if (bannerFile == null) continue; - { - // Create a reference for the banner file - var reference = new CloudFileReference - { - FileId = bannerFile.Id, - File = bannerFile, - Usage = "profile.background", - ResourceId = profile.Id.ToString() - }; - - await db.FileReferences.AddAsync(reference); - profile.Background = bannerFile.ToReferenceObject(); - db.AccountProfiles.Update(profile); - } - } - - await db.SaveChangesAsync(); - } - - private async Task ScanChatRooms() - { - var chatRooms = await db.ChatRooms - .Where(c => c.PictureId != null || c.BackgroundId != null) - .ToListAsync(); - - foreach (var chatRoom in chatRooms) - { - if (chatRoom is { PictureId: not null, Picture: null }) - { - var avatarFile = await db.Files.FirstOrDefaultAsync(f => f.Id == chatRoom.PictureId); - if (avatarFile != null) - { - // Create a reference for the avatar file - var reference = new CloudFileReference - { - FileId = avatarFile.Id, - File = avatarFile, - Usage = "chatroom.picture", - ResourceId = chatRoom.ResourceIdentifier - }; - - await db.FileReferences.AddAsync(reference); - chatRoom.Picture = avatarFile.ToReferenceObject(); - db.ChatRooms.Update(chatRoom); - } - } - - if (chatRoom is not { BackgroundId: not null, Background: null }) continue; - var bannerFile = await db.Files.FirstOrDefaultAsync(f => f.Id == chatRoom.BackgroundId); - if (bannerFile == null) continue; - { - // Create a reference for the banner file - var reference = new CloudFileReference - { - FileId = bannerFile.Id, - File = bannerFile, - Usage = "chatroom.background", - ResourceId = chatRoom.ResourceIdentifier - }; - - await db.FileReferences.AddAsync(reference); - chatRoom.Background = bannerFile.ToReferenceObject(); - db.ChatRooms.Update(chatRoom); - } - } - - await db.SaveChangesAsync(); - } - - private async Task ScanRealms() - { - var realms = await db.Realms - .Where(r => r.PictureId != null && r.BackgroundId != null) - .ToListAsync(); - - foreach (var realm in realms) - { - // Process avatar if it exists - if (realm is { PictureId: not null, Picture: null }) - { - var avatarFile = await db.Files.FirstOrDefaultAsync(f => f.Id == realm.PictureId); - if (avatarFile != null) - { - // Create a reference for the avatar file - var reference = new CloudFileReference - { - FileId = avatarFile.Id, - File = avatarFile, - Usage = "realm.picture", - ResourceId = realm.ResourceIdentifier - }; - - await db.FileReferences.AddAsync(reference); - realm.Picture = avatarFile.ToReferenceObject(); - } - } - - // Process banner if it exists - if (realm is { BackgroundId: not null, Background: null }) - { - var bannerFile = await db.Files.FirstOrDefaultAsync(f => f.Id == realm.BackgroundId); - if (bannerFile != null) - { - // Create a reference for the banner file - var reference = new CloudFileReference - { - FileId = bannerFile.Id, - File = bannerFile, - Usage = "realm.background", - ResourceId = realm.ResourceIdentifier - }; - - await db.FileReferences.AddAsync(reference); - realm.Background = bannerFile.ToReferenceObject(); - } - } - - db.Realms.Update(realm); - } - - await db.SaveChangesAsync(); - } - - private async Task ScanPublishers() - { - var publishers = await db.Publishers - .Where(p => p.PictureId != null || p.BackgroundId != null) - .ToListAsync(); - - foreach (var publisher in publishers) - { - if (publisher is { PictureId: not null, Picture: null }) - { - var pictureFile = await db.Files.FirstOrDefaultAsync(f => f.Id == publisher.PictureId); - if (pictureFile != null) - { - // Create a reference for the picture file - var reference = new CloudFileReference - { - FileId = pictureFile.Id, - File = pictureFile, - Usage = "publisher.picture", - ResourceId = publisher.Id.ToString() - }; - - await db.FileReferences.AddAsync(reference); - publisher.Picture = pictureFile.ToReferenceObject(); - } - } - - if (publisher is { BackgroundId: not null, Background: null }) - { - var backgroundFile = await db.Files.FirstOrDefaultAsync(f => f.Id == publisher.BackgroundId); - if (backgroundFile != null) - { - // Create a reference for the background file - var reference = new CloudFileReference - { - FileId = backgroundFile.Id, - File = backgroundFile, - Usage = "publisher.background", - ResourceId = publisher.ResourceIdentifier - }; - - await db.FileReferences.AddAsync(reference); - publisher.Background = backgroundFile.ToReferenceObject(); - } - } - - db.Publishers.Update(publisher); - } - - await db.SaveChangesAsync(); - } - - private async Task ScanStickers() - { - var stickers = await db.Stickers - .Where(s => s.ImageId != null && s.Image == null) - .ToListAsync(); - - foreach (var sticker in stickers) - { - var imageFile = await db.Files.FirstOrDefaultAsync(f => f.Id == sticker.ImageId); - if (imageFile != null) - { - // Create a reference for the sticker image file - var reference = new CloudFileReference - { - FileId = imageFile.Id, - File = imageFile, - Usage = "sticker.image", - ResourceId = sticker.ResourceIdentifier - }; - - await db.FileReferences.AddAsync(reference); - sticker.Image = imageFile.ToReferenceObject(); - db.Stickers.Update(sticker); - } - } - - await db.SaveChangesAsync(); - } -} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Storage/FileService.cs b/DysonNetwork.Sphere/Storage/FileService.cs deleted file mode 100644 index d7d70fa..0000000 --- a/DysonNetwork.Sphere/Storage/FileService.cs +++ /dev/null @@ -1,555 +0,0 @@ -using System.Globalization; -using FFMpegCore; -using System.Security.Cryptography; -using AngleSharp.Text; -using Microsoft.EntityFrameworkCore; -using Minio; -using Minio.DataModel.Args; -using NetVips; -using NodaTime; -using tusdotnet.Stores; - -namespace DysonNetwork.Sphere.Storage; - -public class FileService( - AppDatabase db, - IConfiguration configuration, - TusDiskStore store, - ILogger logger, - IServiceScopeFactory scopeFactory, - ICacheService cache -) -{ - private const string CacheKeyPrefix = "file:"; - private static readonly TimeSpan CacheDuration = TimeSpan.FromMinutes(15); - - /// - /// The api for getting file meta with cache, - /// the best use case is for accessing the file data. - /// - /// This function won't load uploader's information, only keep minimal file meta - /// - /// The id of the cloud file requested - /// The minimal file meta - public async Task GetFileAsync(string fileId) - { - var cacheKey = $"{CacheKeyPrefix}{fileId}"; - - var cachedFile = await cache.GetAsync(cacheKey); - if (cachedFile is not null) - return cachedFile; - - var file = await db.Files - .Include(f => f.Account) - .Where(f => f.Id == fileId) - .FirstOrDefaultAsync(); - - if (file != null) - await cache.SetAsync(cacheKey, file, CacheDuration); - - return file; - } - - private static readonly string TempFilePrefix = "dyn-cloudfile"; - - private static readonly string[] AnimatedImageTypes = - ["image/gif", "image/apng", "image/webp", "image/avif"]; - - // The analysis file method no longer will remove the GPS EXIF data - // It should be handled on the client side, and for some specific cases it should be keep - public async Task ProcessNewFileAsync( - Account.Account account, - string fileId, - Stream stream, - string fileName, - string? contentType - ) - { - var result = new List<(string filePath, string suffix)>(); - - var ogFilePath = Path.GetFullPath(Path.Join(configuration.GetValue("Tus:StorePath"), fileId)); - var fileSize = stream.Length; - var hash = await HashFileAsync(stream, fileSize: fileSize); - contentType ??= !fileName.Contains('.') ? "application/octet-stream" : MimeTypes.GetMimeType(fileName); - - var file = new CloudFile - { - Id = fileId, - Name = fileName, - MimeType = contentType, - Size = fileSize, - Hash = hash, - AccountId = account.Id - }; - - var existingFile = await db.Files.FirstOrDefaultAsync(f => f.Hash == hash); - file.StorageId = existingFile is not null ? existingFile.StorageId : file.Id; - - if (existingFile is not null) - { - file.FileMeta = existingFile.FileMeta; - file.HasCompression = existingFile.HasCompression; - file.SensitiveMarks = existingFile.SensitiveMarks; - - db.Files.Add(file); - await db.SaveChangesAsync(); - return file; - } - - switch (contentType.Split('/')[0]) - { - case "image": - var blurhash = - BlurHashSharp.SkiaSharp.BlurHashEncoder.Encode(xComponent: 3, yComponent: 3, filename: ogFilePath); - - // Rewind stream - stream.Position = 0; - - // Use NetVips for the rest - using (var vipsImage = NetVips.Image.NewFromStream(stream)) - { - var width = vipsImage.Width; - var height = vipsImage.Height; - var format = vipsImage.Get("vips-loader") ?? "unknown"; - - // Try to get orientation from exif data - var orientation = 1; - var meta = new Dictionary - { - ["blur"] = blurhash, - ["format"] = format, - ["width"] = width, - ["height"] = height, - ["orientation"] = orientation, - }; - Dictionary exif = []; - - foreach (var field in vipsImage.GetFields()) - { - var value = vipsImage.Get(field); - - // Skip GPS-related EXIF fields to remove location data - if (IsIgnoredField(field)) - continue; - - if (field.StartsWith("exif-")) exif[field.Replace("exif-", "")] = value; - else meta[field] = value; - - if (field == "orientation") orientation = (int)value; - } - - if (orientation is 6 or 8) - (width, height) = (height, width); - - var aspectRatio = height != 0 ? (double)width / height : 0; - - meta["exif"] = exif; - meta["ratio"] = aspectRatio; - file.FileMeta = meta; - } - - break; - case "video": - case "audio": - try - { - var mediaInfo = await FFProbe.AnalyseAsync(ogFilePath); - file.FileMeta = new Dictionary - { - ["duration"] = mediaInfo.Duration.TotalSeconds, - ["format_name"] = mediaInfo.Format.FormatName, - ["format_long_name"] = mediaInfo.Format.FormatLongName, - ["start_time"] = mediaInfo.Format.StartTime.ToString(), - ["bit_rate"] = mediaInfo.Format.BitRate.ToString(CultureInfo.InvariantCulture), - ["tags"] = mediaInfo.Format.Tags ?? [], - ["chapters"] = mediaInfo.Chapters, - }; - if (mediaInfo.PrimaryVideoStream is not null) - file.FileMeta["ratio"] = mediaInfo.PrimaryVideoStream.Width / mediaInfo.PrimaryVideoStream.Height; - } - catch (Exception ex) - { - logger.LogError("File analyzed failed, unable collect video / audio information: {Message}", - ex.Message); - } - - break; - } - - db.Files.Add(file); - await db.SaveChangesAsync(); - - _ = Task.Run(async () => - { - using var scope = scopeFactory.CreateScope(); - var nfs = scope.ServiceProvider.GetRequiredService(); - - try - { - logger.LogInformation("Processed file {fileId}, now trying optimizing if possible...", fileId); - - if (contentType.Split('/')[0] == "image") - { - // Skip compression for animated image types - var animatedMimeTypes = AnimatedImageTypes; - if (Enumerable.Contains(animatedMimeTypes, contentType)) - { - logger.LogInformation( - "File {fileId} is an animated image (MIME: {mime}), skipping WebP conversion.", fileId, - contentType - ); - var tempFilePath = Path.Join(Path.GetTempPath(), $"{TempFilePrefix}#{file.Id}"); - result.Add((tempFilePath, string.Empty)); - return; - } - - file.MimeType = "image/webp"; - - using var vipsImage = Image.NewFromFile(ogFilePath); - var imagePath = Path.Join(Path.GetTempPath(), $"{TempFilePrefix}#{file.Id}"); - vipsImage.Autorot().WriteToFile(imagePath + ".webp", - new VOption { { "lossless", true }, { "strip", true } }); - result.Add((imagePath + ".webp", string.Empty)); - - if (vipsImage.Width * vipsImage.Height >= 1024 * 1024) - { - var scale = 1024.0 / Math.Max(vipsImage.Width, vipsImage.Height); - var imageCompressedPath = - Path.Join(Path.GetTempPath(), $"{TempFilePrefix}#{file.Id}-compressed"); - - // Create and save image within the same synchronous block to avoid disposal issues - using var compressedImage = vipsImage.Resize(scale); - compressedImage.Autorot().WriteToFile(imageCompressedPath + ".webp", - new VOption { { "Q", 80 }, { "strip", true } }); - - result.Add((imageCompressedPath + ".webp", ".compressed")); - file.HasCompression = true; - } - } - else - { - // No extra process for video, add it to the upload queue. - result.Add((ogFilePath, string.Empty)); - } - - logger.LogInformation("Optimized file {fileId}, now uploading...", fileId); - - if (result.Count > 0) - { - List> tasks = []; - tasks.AddRange(result.Select(item => - nfs.UploadFileToRemoteAsync(file, item.filePath, null, item.suffix, true)) - ); - - await Task.WhenAll(tasks); - file = await tasks.First(); - } - else - { - file = await nfs.UploadFileToRemoteAsync(file, stream, null); - } - - logger.LogInformation("Uploaded file {fileId} done!", fileId); - - var scopedDb = scope.ServiceProvider.GetRequiredService(); - await scopedDb.Files.Where(f => f.Id == file.Id).ExecuteUpdateAsync(setter => setter - .SetProperty(f => f.UploadedAt, file.UploadedAt) - .SetProperty(f => f.UploadedTo, file.UploadedTo) - .SetProperty(f => f.MimeType, file.MimeType) - .SetProperty(f => f.HasCompression, file.HasCompression) - ); - } - catch (Exception err) - { - logger.LogError(err, "Failed to process {fileId}", fileId); - } - - await stream.DisposeAsync(); - await store.DeleteFileAsync(file.Id, CancellationToken.None); - await nfs._PurgeCacheAsync(file.Id); - }); - - return file; - } - - private static async Task HashFileAsync(Stream stream, int chunkSize = 1024 * 1024, long? fileSize = null) - { - fileSize ??= stream.Length; - if (fileSize > chunkSize * 1024 * 5) - return await HashFastApproximateAsync(stream, chunkSize); - - using var md5 = MD5.Create(); - var hashBytes = await md5.ComputeHashAsync(stream); - return Convert.ToHexString(hashBytes).ToLowerInvariant(); - } - - private static async Task HashFastApproximateAsync(Stream stream, int chunkSize = 1024 * 1024) - { - // Scale the chunk size to kB level - chunkSize *= 1024; - - using var md5 = MD5.Create(); - - var buffer = new byte[chunkSize * 2]; - var fileLength = stream.Length; - - var bytesRead = await stream.ReadAsync(buffer.AsMemory(0, chunkSize)); - - if (fileLength > chunkSize) - { - stream.Seek(-chunkSize, SeekOrigin.End); - bytesRead += await stream.ReadAsync(buffer.AsMemory(chunkSize, chunkSize)); - } - - var hash = md5.ComputeHash(buffer, 0, bytesRead); - return Convert.ToHexString(hash).ToLowerInvariant(); - } - - public async Task UploadFileToRemoteAsync(CloudFile file, string filePath, string? targetRemote, - string? suffix = null, bool selfDestruct = false) - { - var fileStream = File.OpenRead(filePath); - var result = await UploadFileToRemoteAsync(file, fileStream, targetRemote, suffix); - if (selfDestruct) File.Delete(filePath); - return result; - } - - public async Task UploadFileToRemoteAsync(CloudFile file, Stream stream, string? targetRemote, - string? suffix = null) - { - if (file.UploadedAt.HasValue) return file; - - file.UploadedTo = targetRemote ?? configuration.GetValue("Storage:PreferredRemote")!; - - var dest = GetRemoteStorageConfig(file.UploadedTo); - var client = CreateMinioClient(dest); - if (client is null) - throw new InvalidOperationException( - $"Failed to configure client for remote destination '{file.UploadedTo}'" - ); - - var bucket = dest.Bucket; - var contentType = file.MimeType ?? "application/octet-stream"; - - await client.PutObjectAsync(new PutObjectArgs() - .WithBucket(bucket) - .WithObject(string.IsNullOrWhiteSpace(suffix) ? file.Id : file.Id + suffix) - .WithStreamData(stream) // Fix this disposed - .WithObjectSize(stream.Length) - .WithContentType(contentType) - ); - - file.UploadedAt = Instant.FromDateTimeUtc(DateTime.UtcNow); - return file; - } - - public async Task DeleteFileAsync(CloudFile file) - { - await DeleteFileDataAsync(file); - - db.Remove(file); - await db.SaveChangesAsync(); - await _PurgeCacheAsync(file.Id); - } - - public async Task DeleteFileDataAsync(CloudFile file) - { - if (file.StorageId is null) return; - if (file.UploadedTo is null) return; - - // Check if any other file with the same storage ID is referenced - var otherFilesWithSameStorageId = await db.Files - .Where(f => f.StorageId == file.StorageId && f.Id != file.Id) - .Select(f => f.Id) - .ToListAsync(); - - // Check if any of these files are referenced - var anyReferenced = false; - if (otherFilesWithSameStorageId.Any()) - { - anyReferenced = await db.FileReferences - .Where(r => otherFilesWithSameStorageId.Contains(r.FileId)) - .AnyAsync(); - } - - // If any other file with the same storage ID is referenced, don't delete the actual file data - if (anyReferenced) return; - - var dest = GetRemoteStorageConfig(file.UploadedTo); - var client = CreateMinioClient(dest); - if (client is null) - throw new InvalidOperationException( - $"Failed to configure client for remote destination '{file.UploadedTo}'" - ); - - var bucket = dest.Bucket; - var objectId = file.StorageId ?? file.Id; // Use StorageId if available, otherwise fall back to Id - - await client.RemoveObjectAsync( - new RemoveObjectArgs().WithBucket(bucket).WithObject(objectId) - ); - - if (file.HasCompression) - { - // Also remove the compressed version if it exists - try - { - await client.RemoveObjectAsync( - new RemoveObjectArgs().WithBucket(bucket).WithObject(objectId + ".compressed") - ); - } - catch - { - // Ignore errors when deleting compressed version - logger.LogWarning("Failed to delete compressed version of file {fileId}", file.Id); - } - } - } - - public RemoteStorageConfig GetRemoteStorageConfig(string destination) - { - var destinations = configuration.GetSection("Storage:Remote").Get>()!; - var dest = destinations.FirstOrDefault(d => d.Id == destination); - if (dest is null) throw new InvalidOperationException($"Remote destination '{destination}' not found"); - return dest; - } - - public IMinioClient? CreateMinioClient(RemoteStorageConfig dest) - { - var client = new MinioClient() - .WithEndpoint(dest.Endpoint) - .WithRegion(dest.Region) - .WithCredentials(dest.SecretId, dest.SecretKey); - if (dest.EnableSsl) client = client.WithSSL(); - - return client.Build(); - } - - // Helper method to purge the cache for a specific file - // Made internal to allow FileReferenceService to use it - internal async Task _PurgeCacheAsync(string fileId) - { - var cacheKey = $"{CacheKeyPrefix}{fileId}"; - await cache.RemoveAsync(cacheKey); - } - - // Helper method to purge cache for multiple files - internal async Task _PurgeCacheRangeAsync(IEnumerable fileIds) - { - var tasks = fileIds.Select(_PurgeCacheAsync); - await Task.WhenAll(tasks); - } - - public async Task> LoadFromReference(List references) - { - var cachedFiles = new Dictionary(); - var uncachedIds = new List(); - - // Check cache first - foreach (var reference in references) - { - var cacheKey = $"{CacheKeyPrefix}{reference.Id}"; - var cachedFile = await cache.GetAsync(cacheKey); - - if (cachedFile != null) - { - cachedFiles[reference.Id] = cachedFile; - } - else - { - uncachedIds.Add(reference.Id); - } - } - - // Load uncached files from database - if (uncachedIds.Count > 0) - { - var dbFiles = await db.Files - .Include(f => f.Account) - .Where(f => uncachedIds.Contains(f.Id)) - .ToListAsync(); - - // Add to cache - foreach (var file in dbFiles) - { - var cacheKey = $"{CacheKeyPrefix}{file.Id}"; - await cache.SetAsync(cacheKey, file, CacheDuration); - cachedFiles[file.Id] = file; - } - } - - // Preserve original order - return references - .Select(r => cachedFiles.GetValueOrDefault(r.Id)) - .Where(f => f != null) - .ToList(); - } - - /// - /// Gets the number of references to a file based on CloudFileReference records - /// - /// The ID of the file - /// The number of references to the file - public async Task GetReferenceCountAsync(string fileId) - { - return await db.FileReferences - .Where(r => r.FileId == fileId) - .CountAsync(); - } - - /// - /// Checks if a file is referenced by any resource - /// - /// The ID of the file to check - /// True if the file is referenced, false otherwise - public async Task IsReferencedAsync(string fileId) - { - return await db.FileReferences - .Where(r => r.FileId == fileId) - .AnyAsync(); - } - - /// - /// Checks if an EXIF field contains GPS location data - /// - /// The EXIF field name - /// True if the field contains GPS data, false otherwise - private static bool IsGpsExifField(string fieldName) - { - // Common GPS EXIF field names - var gpsFields = new[] - { - "gps-latitude", - "gps-longitude", - "gps-altitude", - "gps-latitude-ref", - "gps-longitude-ref", - "gps-altitude-ref", - "gps-timestamp", - "gps-datestamp", - "gps-speed", - "gps-speed-ref", - "gps-track", - "gps-track-ref", - "gps-img-direction", - "gps-img-direction-ref", - "gps-dest-latitude", - "gps-dest-longitude", - "gps-dest-latitude-ref", - "gps-dest-longitude-ref", - "gps-processing-method", - "gps-area-information" - }; - - return gpsFields.Any(gpsField => - fieldName.Equals(gpsField, StringComparison.OrdinalIgnoreCase) || - fieldName.StartsWith("gps", StringComparison.OrdinalIgnoreCase)); - } - - private static bool IsIgnoredField(string fieldName) - { - if (IsGpsExifField(fieldName)) return true; - if (fieldName.EndsWith("-data")) return true; - return false; - } -} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Storage/FlushBufferService.cs b/DysonNetwork.Sphere/Storage/FlushBufferService.cs deleted file mode 100644 index 43dd6d8..0000000 --- a/DysonNetwork.Sphere/Storage/FlushBufferService.cs +++ /dev/null @@ -1,66 +0,0 @@ -using System.Collections.Concurrent; - -namespace DysonNetwork.Sphere.Storage; - -public interface IFlushHandler -{ - Task FlushAsync(IReadOnlyList items); -} - -public class FlushBufferService -{ - private readonly Dictionary _buffers = new(); - private readonly Lock _lockObject = new(); - - private ConcurrentQueue _GetOrCreateBuffer() - { - var type = typeof(T); - lock (_lockObject) - { - if (!_buffers.TryGetValue(type, out var buffer)) - { - buffer = new ConcurrentQueue(); - _buffers[type] = buffer; - } - return (ConcurrentQueue)buffer; - } - } - - public void Enqueue(T item) - { - var buffer = _GetOrCreateBuffer(); - buffer.Enqueue(item); - } - - public async Task FlushAsync(IFlushHandler handler) - { - var buffer = _GetOrCreateBuffer(); - var workingQueue = new List(); - - while (buffer.TryDequeue(out var item)) - { - workingQueue.Add(item); - } - - if (workingQueue.Count == 0) - return; - - try - { - await handler.FlushAsync(workingQueue); - } - catch (Exception) - { - // If flush fails, re-queue the items - foreach (var item in workingQueue) - buffer.Enqueue(item); - throw; - } - } - - public int GetPendingCount() - { - var buffer = _GetOrCreateBuffer(); - return buffer.Count; - } -} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Storage/Handlers/ActionLogFlushHandler.cs b/DysonNetwork.Sphere/Storage/Handlers/ActionLogFlushHandler.cs deleted file mode 100644 index f71e8a4..0000000 --- a/DysonNetwork.Sphere/Storage/Handlers/ActionLogFlushHandler.cs +++ /dev/null @@ -1,24 +0,0 @@ -using DysonNetwork.Sphere.Account; -using EFCore.BulkExtensions; -using Quartz; - -namespace DysonNetwork.Sphere.Storage.Handlers; - -public class ActionLogFlushHandler(IServiceProvider serviceProvider) : IFlushHandler -{ - public async Task FlushAsync(IReadOnlyList items) - { - using var scope = serviceProvider.CreateScope(); - var db = scope.ServiceProvider.GetRequiredService(); - - await db.BulkInsertAsync(items, config => config.ConflictOption = ConflictOption.Ignore); - } -} - -public class ActionLogFlushJob(FlushBufferService fbs, ActionLogFlushHandler hdl) : IJob -{ - public async Task Execute(IJobExecutionContext context) - { - await fbs.FlushAsync(hdl); - } -} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Storage/Handlers/LastActiveFlushHandler.cs b/DysonNetwork.Sphere/Storage/Handlers/LastActiveFlushHandler.cs deleted file mode 100644 index 9546c03..0000000 --- a/DysonNetwork.Sphere/Storage/Handlers/LastActiveFlushHandler.cs +++ /dev/null @@ -1,60 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using NodaTime; -using Quartz; - -namespace DysonNetwork.Sphere.Storage.Handlers; - -public class LastActiveInfo -{ - public Auth.Session Session { get; set; } = null!; - public Account.Account Account { get; set; } = null!; - public Instant SeenAt { get; set; } -} - -public class LastActiveFlushHandler(IServiceProvider serviceProvider) : IFlushHandler -{ - public async Task FlushAsync(IReadOnlyList items) - { - using var scope = serviceProvider.CreateScope(); - var db = scope.ServiceProvider.GetRequiredService(); - - // Remove duplicates by grouping on (sessionId, accountId), taking the most recent SeenAt - var distinctItems = items - .GroupBy(x => (SessionId: x.Session.Id, AccountId: x.Account.Id)) - .Select(g => g.OrderByDescending(x => x.SeenAt).First()) - .ToList(); - - // Build dictionaries so we can match session/account IDs to their new "last seen" timestamps - var sessionIdMap = distinctItems - .GroupBy(x => x.Session.Id) - .ToDictionary(g => g.Key, g => g.Last().SeenAt); - - var accountIdMap = distinctItems - .GroupBy(x => x.Account.Id) - .ToDictionary(g => g.Key, g => g.Last().SeenAt); - - // Update sessions using native EF Core ExecuteUpdateAsync - foreach (var kvp in sessionIdMap) - { - await db.AuthSessions - .Where(s => s.Id == kvp.Key) - .ExecuteUpdateAsync(s => s.SetProperty(x => x.LastGrantedAt, kvp.Value)); - } - - // Update account profiles using native EF Core ExecuteUpdateAsync - foreach (var kvp in accountIdMap) - { - await db.AccountProfiles - .Where(a => a.AccountId == kvp.Key) - .ExecuteUpdateAsync(a => a.SetProperty(x => x.LastSeenAt, kvp.Value)); - } - } -} - -public class LastActiveFlushJob(FlushBufferService fbs, ActionLogFlushHandler hdl) : IJob -{ - public async Task Execute(IJobExecutionContext context) - { - await fbs.FlushAsync(hdl); - } -} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Storage/Handlers/MessageReadReceiptFlushHandler.cs b/DysonNetwork.Sphere/Storage/Handlers/MessageReadReceiptFlushHandler.cs deleted file mode 100644 index fb07245..0000000 --- a/DysonNetwork.Sphere/Storage/Handlers/MessageReadReceiptFlushHandler.cs +++ /dev/null @@ -1,33 +0,0 @@ -using DysonNetwork.Sphere.Chat; -using EFCore.BulkExtensions; -using Microsoft.EntityFrameworkCore; -using NodaTime; -using Quartz; - -namespace DysonNetwork.Sphere.Storage.Handlers; - -public class MessageReadReceiptFlushHandler(IServiceProvider serviceProvider) : IFlushHandler -{ - public async Task FlushAsync(IReadOnlyList items) - { - var now = SystemClock.Instance.GetCurrentInstant(); - var distinctId = items - .DistinctBy(x => x.SenderId) - .Select(x => x.SenderId) - .ToList(); - - using var scope = serviceProvider.CreateScope(); - var db = scope.ServiceProvider.GetRequiredService(); - await db.ChatMembers.Where(r => distinctId.Contains(r.Id)) - .ExecuteUpdateAsync(s => s.SetProperty(m => m.LastReadAt, now) - ); - } -} - -public class ReadReceiptFlushJob(FlushBufferService fbs, MessageReadReceiptFlushHandler hdl) : IJob -{ - public async Task Execute(IJobExecutionContext context) - { - await fbs.FlushAsync(hdl); - } -} diff --git a/DysonNetwork.Sphere/Storage/Handlers/PostViewFlushHandler.cs b/DysonNetwork.Sphere/Storage/Handlers/PostViewFlushHandler.cs deleted file mode 100644 index 1984ec0..0000000 --- a/DysonNetwork.Sphere/Storage/Handlers/PostViewFlushHandler.cs +++ /dev/null @@ -1,52 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using NodaTime; -using Quartz; - -namespace DysonNetwork.Sphere.Storage.Handlers; - -public class PostViewFlushHandler(IServiceProvider serviceProvider) : IFlushHandler -{ - public async Task FlushAsync(IReadOnlyList items) - { - using var scope = serviceProvider.CreateScope(); - var db = scope.ServiceProvider.GetRequiredService(); - var cache = scope.ServiceProvider.GetRequiredService(); - - // Group views by post - var postViews = items - .GroupBy(x => x.PostId) - .ToDictionary(g => g.Key, g => g.ToList()); - - // Calculate total views and unique views per post - foreach (var postId in postViews.Keys) - { - // Calculate unique views by distinct viewer IDs (not null) - var uniqueViews = postViews[postId] - .Where(v => !string.IsNullOrEmpty(v.ViewerId)) - .Select(v => v.ViewerId) - .Distinct() - .Count(); - - // Total views is just the count of all items for this post - var totalViews = postViews[postId].Count; - - // Update the post in the database - await db.Posts - .Where(p => p.Id == postId) - .ExecuteUpdateAsync(p => p - .SetProperty(x => x.ViewsTotal, x => x.ViewsTotal + totalViews) - .SetProperty(x => x.ViewsUnique, x => x.ViewsUnique + uniqueViews)); - - // Invalidate any cache entries for this post - await cache.RemoveAsync($"post:{postId}"); - } - } -} - -public class PostViewFlushJob(FlushBufferService fbs, PostViewFlushHandler hdl) : IJob -{ - public async Task Execute(IJobExecutionContext context) - { - await fbs.FlushAsync(hdl); - } -} diff --git a/DysonNetwork.Sphere/Storage/ICloudFile.cs b/DysonNetwork.Sphere/Storage/ICloudFile.cs deleted file mode 100644 index 4a10851..0000000 --- a/DysonNetwork.Sphere/Storage/ICloudFile.cs +++ /dev/null @@ -1,55 +0,0 @@ -using NodaTime; - -namespace DysonNetwork.Sphere.Storage; - -/// -/// Common interface for cloud file entities that can be used in file operations. -/// This interface exposes the essential properties needed for file operations -/// and is implemented by both CloudFile and CloudFileReferenceObject. -/// -public interface ICloudFile -{ - public Instant CreatedAt { get; } - public Instant UpdatedAt { get; } - public Instant? DeletedAt { get; } - - /// - /// Gets the unique identifier of the cloud file. - /// - string Id { get; } - - /// - /// Gets the name of the cloud file. - /// - string Name { get; } - - /// - /// Gets the file metadata dictionary. - /// - Dictionary? FileMeta { get; } - - /// - /// Gets the user metadata dictionary. - /// - Dictionary? UserMeta { get; } - - /// - /// Gets the MIME type of the file. - /// - string? MimeType { get; } - - /// - /// Gets the hash of the file content. - /// - string? Hash { get; } - - /// - /// Gets the size of the file in bytes. - /// - long Size { get; } - - /// - /// Gets whether the file has a compressed version available. - /// - bool HasCompression { get; } -} diff --git a/DysonNetwork.Sphere/Storage/TextSanitizer.cs b/DysonNetwork.Sphere/Storage/TextSanitizer.cs deleted file mode 100644 index 82a45c6..0000000 --- a/DysonNetwork.Sphere/Storage/TextSanitizer.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System.Globalization; -using System.Text; - -namespace DysonNetwork.Sphere.Storage; - -public abstract class TextSanitizer -{ - public static string? Sanitize(string? text) - { - if (string.IsNullOrEmpty(text)) return text; - - // List of control characters to preserve - var preserveControlChars = new[] { '\n', '\r', '\t', ' ' }; - - var filtered = new StringBuilder(); - foreach (var ch in text) - { - var category = CharUnicodeInfo.GetUnicodeCategory(ch); - - // Keep whitespace and other specified control characters - if (category is not UnicodeCategory.Control || preserveControlChars.Contains(ch)) - { - // Still filter out Format and NonSpacingMark categories - if (category is not (UnicodeCategory.Format or UnicodeCategory.NonSpacingMark)) - { - filtered.Append(ch); - } - } - } - - return filtered.ToString(); - } -} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Storage/TusService.cs b/DysonNetwork.Sphere/Storage/TusService.cs deleted file mode 100644 index 6c25c0b..0000000 --- a/DysonNetwork.Sphere/Storage/TusService.cs +++ /dev/null @@ -1,78 +0,0 @@ -using System.Net; -using System.Text; -using System.Text.Json; -using DysonNetwork.Sphere.Permission; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Options; -using tusdotnet.Interfaces; -using tusdotnet.Models; -using tusdotnet.Models.Configuration; - -namespace DysonNetwork.Sphere.Storage; - -public abstract class TusService -{ - public static DefaultTusConfiguration BuildConfiguration(ITusStore store) => new() - { - Store = store, - Events = new Events - { - OnAuthorizeAsync = async eventContext => - { - if (eventContext.Intent == IntentType.DeleteFile) - { - eventContext.FailRequest( - HttpStatusCode.BadRequest, - "Deleting files from this endpoint was disabled, please refer to the Dyson Network File API." - ); - return; - } - - var httpContext = eventContext.HttpContext; - if (httpContext.Items["CurrentUser"] is not Account.Account user) - { - eventContext.FailRequest(HttpStatusCode.Unauthorized); - return; - } - - if (!user.IsSuperuser) - { - using var scope = httpContext.RequestServices.CreateScope(); - var pm = scope.ServiceProvider.GetRequiredService(); - var allowed = await pm.HasPermissionAsync($"user:{user.Id}", "global", "files.create"); - if (!allowed) - eventContext.FailRequest(HttpStatusCode.Forbidden); - } - }, - OnFileCompleteAsync = async eventContext => - { - using var scope = eventContext.HttpContext.RequestServices.CreateScope(); - var services = scope.ServiceProvider; - - var httpContext = eventContext.HttpContext; - if (httpContext.Items["CurrentUser"] is not Account.Account user) return; - - var file = await eventContext.GetFileAsync(); - var metadata = await file.GetMetadataAsync(eventContext.CancellationToken); - var fileName = metadata.TryGetValue("filename", out var fn) - ? fn.GetString(Encoding.UTF8) - : "uploaded_file"; - var contentType = metadata.TryGetValue("content-type", out var ct) ? ct.GetString(Encoding.UTF8) : null; - - var fileStream = await file.GetContentAsync(eventContext.CancellationToken); - - var fileService = services.GetRequiredService(); - var info = await fileService.ProcessNewFileAsync(user, file.Id, fileStream, fileName, contentType); - - using var finalScope = eventContext.HttpContext.RequestServices.CreateScope(); - var jsonOptions = finalScope.ServiceProvider.GetRequiredService>().Value - .JsonSerializerOptions; - var infoJson = JsonSerializer.Serialize(info, jsonOptions); - eventContext.HttpContext.Response.Headers.Append("X-FileInfo", infoJson); - - // Dispose the stream after all processing is complete - await fileStream.DisposeAsync(); - } - } - }; -} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Wallet/OrderController.cs b/DysonNetwork.Sphere/Wallet/OrderController.cs deleted file mode 100644 index 7200fac..0000000 --- a/DysonNetwork.Sphere/Wallet/OrderController.cs +++ /dev/null @@ -1,57 +0,0 @@ -using DysonNetwork.Sphere.Auth; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; - -namespace DysonNetwork.Sphere.Wallet; - -[ApiController] -[Route("/api/orders")] -public class OrderController(PaymentService payment, AuthService auth, AppDatabase db) : ControllerBase -{ - [HttpGet("{id:guid}")] - public async Task> GetOrderById(Guid id) - { - var order = await db.PaymentOrders.FindAsync(id); - - if (order == null) - { - return NotFound(); - } - - return Ok(order); - } - - [HttpPost("{id:guid}/pay")] - [Authorize] - public async Task> PayOrder(Guid id, [FromBody] PayOrderRequest request) - { - if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser || - HttpContext.Items["CurrentSession"] is not Session currentSession) return Unauthorized(); - - // Validate PIN code - if (!await auth.ValidatePinCode(currentUser.Id, request.PinCode)) - return StatusCode(403, "Invalid PIN Code"); - - try - { - // Get the wallet for the current user - var wallet = await db.Wallets.FirstOrDefaultAsync(w => w.AccountId == currentUser.Id); - if (wallet == null) - return BadRequest("Wallet was not found."); - - // Pay the order - var paidOrder = await payment.PayOrderAsync(id, wallet.Id); - return Ok(paidOrder); - } - catch (InvalidOperationException ex) - { - return BadRequest(new { error = ex.Message }); - } - } -} - -public class PayOrderRequest -{ - public string PinCode { get; set; } = string.Empty; -} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Wallet/Payment.cs b/DysonNetwork.Sphere/Wallet/Payment.cs deleted file mode 100644 index d3a2a94..0000000 --- a/DysonNetwork.Sphere/Wallet/Payment.cs +++ /dev/null @@ -1,63 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; -using DysonNetwork.Sphere.Developer; -using NodaTime; - -namespace DysonNetwork.Sphere.Wallet; - -public class WalletCurrency -{ - public const string SourcePoint = "points"; - public const string GoldenPoint = "golds"; -} - -public enum OrderStatus -{ - Unpaid, - Paid, - Cancelled, - Finished, - Expired -} - -public class Order : ModelBase -{ - public Guid Id { get; set; } = Guid.NewGuid(); - public OrderStatus Status { get; set; } = OrderStatus.Unpaid; - [MaxLength(128)] public string Currency { get; set; } = null!; - [MaxLength(4096)] public string? Remarks { get; set; } - [MaxLength(4096)] public string? AppIdentifier { get; set; } - [Column(TypeName = "jsonb")] public Dictionary? Meta { get; set; } - public decimal Amount { get; set; } - public Instant ExpiredAt { get; set; } - - public Guid? PayeeWalletId { get; set; } - public Wallet? PayeeWallet { get; set; } = null!; - public Guid? TransactionId { get; set; } - public Transaction? Transaction { get; set; } - public Guid? IssuerAppId { get; set; } - public CustomApp? IssuerApp { get; set; } -} - -public enum TransactionType -{ - System, - Transfer, - Order -} - -public class Transaction : ModelBase -{ - public Guid Id { get; set; } = Guid.NewGuid(); - [MaxLength(128)] public string Currency { get; set; } = null!; - public decimal Amount { get; set; } - [MaxLength(4096)] public string? Remarks { get; set; } - public TransactionType Type { get; set; } - - // When the payer is null, it's pay from the system - public Guid? PayerWalletId { get; set; } - public Wallet? PayerWallet { get; set; } - // When the payee is null, it's pay for the system - public Guid? PayeeWalletId { get; set; } - public Wallet? PayeeWallet { get; set; } -} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Wallet/PaymentHandlers/AfdianPaymentHandler.cs b/DysonNetwork.Sphere/Wallet/PaymentHandlers/AfdianPaymentHandler.cs deleted file mode 100644 index b97ab99..0000000 --- a/DysonNetwork.Sphere/Wallet/PaymentHandlers/AfdianPaymentHandler.cs +++ /dev/null @@ -1,446 +0,0 @@ -using System.Security.Cryptography; -using System.Text; -using System.Text.Json; -using System.Text.Json.Serialization; -using Microsoft.EntityFrameworkCore; -using NodaTime; - -namespace DysonNetwork.Sphere.Wallet.PaymentHandlers; - -public class AfdianPaymentHandler( - IHttpClientFactory httpClientFactory, - ILogger logger, - IConfiguration configuration -) -{ - private readonly IHttpClientFactory _httpClientFactory = - httpClientFactory ?? throw new ArgumentNullException(nameof(httpClientFactory)); - - private readonly ILogger _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - - private readonly IConfiguration _configuration = - configuration ?? throw new ArgumentNullException(nameof(configuration)); - - private static readonly JsonSerializerOptions JsonOptions = new() - { - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - PropertyNameCaseInsensitive = true - }; - - private string CalculateSign(string token, string userId, string paramsJson, long ts) - { - var kvString = $"{token}params{paramsJson}ts{ts}user_id{userId}"; - using (var md5 = MD5.Create()) - { - var hashBytes = md5.ComputeHash(Encoding.UTF8.GetBytes(kvString)); - return BitConverter.ToString(hashBytes).Replace("-", "").ToLower(); - } - } - - public async Task ListOrderAsync(int page = 1) - { - try - { - var token = _configuration["Payment:Auth:Afdian"] ?? "_:_"; - var tokenParts = token.Split(':'); - var userId = tokenParts[0]; - token = tokenParts[1]; - var paramsJson = JsonSerializer.Serialize(new { page }, JsonOptions); - var ts = (long)(DateTime.UtcNow - new DateTime(1970, 1, 1)) - .TotalSeconds; // Current timestamp in seconds - - var sign = CalculateSign(token, userId, paramsJson, ts); - - var client = _httpClientFactory.CreateClient(); - var request = new HttpRequestMessage(HttpMethod.Post, "https://afdian.com/api/open/query-order") - { - Content = new StringContent(JsonSerializer.Serialize(new - { - user_id = userId, - @params = paramsJson, - ts, - sign - }, JsonOptions), Encoding.UTF8, "application/json") - }; - - var response = await client.SendAsync(request); - if (!response.IsSuccessStatusCode) - { - _logger.LogError( - $"Response Error: {response.StatusCode}, {await response.Content.ReadAsStringAsync()}"); - return null; - } - - var result = await JsonSerializer.DeserializeAsync( - await response.Content.ReadAsStreamAsync(), JsonOptions); - return result; - } - catch (Exception ex) - { - _logger.LogError(ex, "Error fetching orders"); - throw; - } - } - - /// - /// Get a specific order by its ID (out_trade_no) - /// - /// The order ID to query - /// The order item if found, otherwise null - public async Task GetOrderAsync(string orderId) - { - if (string.IsNullOrEmpty(orderId)) - { - _logger.LogWarning("Order ID cannot be null or empty"); - return null; - } - - try - { - var token = _configuration["Payment:Auth:Afdian"] ?? "_:_"; - var tokenParts = token.Split(':'); - var userId = tokenParts[0]; - token = tokenParts[1]; - var paramsJson = JsonSerializer.Serialize(new { out_trade_no = orderId }, JsonOptions); - var ts = (long)(DateTime.UtcNow - new DateTime(1970, 1, 1)) - .TotalSeconds; // Current timestamp in seconds - - var sign = CalculateSign(token, userId, paramsJson, ts); - - var client = _httpClientFactory.CreateClient(); - var request = new HttpRequestMessage(HttpMethod.Post, "https://afdian.com/api/open/query-order") - { - Content = new StringContent(JsonSerializer.Serialize(new - { - user_id = userId, - @params = paramsJson, - ts, - sign - }, JsonOptions), Encoding.UTF8, "application/json") - }; - - var response = await client.SendAsync(request); - if (!response.IsSuccessStatusCode) - { - _logger.LogError( - $"Response Error: {response.StatusCode}, {await response.Content.ReadAsStringAsync()}"); - return null; - } - - var result = await JsonSerializer.DeserializeAsync( - await response.Content.ReadAsStreamAsync(), JsonOptions); - - // Check if we have a valid response and orders in the list - if (result?.Data.Orders == null || result.Data.Orders.Count == 0) - { - _logger.LogWarning($"No order found with ID: {orderId}"); - return null; - } - - // Since we're querying by a specific order ID, we should only get one result - return result.Data.Orders.FirstOrDefault(); - } - catch (Exception ex) - { - _logger.LogError(ex, $"Error fetching order with ID: {orderId}"); - throw; - } - } - - /// - /// Get multiple orders by their IDs (out_trade_no) - /// - /// A collection of order IDs to query - /// A list of found order items - public async Task> GetOrderBatchAsync(IEnumerable orderIds) - { - var orders = orderIds.ToList(); - if (orders.Count == 0) - { - _logger.LogWarning("Order IDs cannot be null or empty"); - return []; - } - - try - { - // Join the order IDs with commas as specified in the API documentation - var orderIdsParam = string.Join(",", orders); - - var token = _configuration["Payment:Auth:Afdian"] ?? "_:_"; - var tokenParts = token.Split(':'); - var userId = tokenParts[0]; - token = tokenParts[1]; - var paramsJson = JsonSerializer.Serialize(new { out_trade_no = orderIdsParam }, JsonOptions); - var ts = (long)(DateTime.UtcNow - new DateTime(1970, 1, 1)) - .TotalSeconds; // Current timestamp in seconds - - var sign = CalculateSign(token, userId, paramsJson, ts); - - var client = _httpClientFactory.CreateClient(); - var request = new HttpRequestMessage(HttpMethod.Post, "https://afdian.com/api/open/query-order") - { - Content = new StringContent(JsonSerializer.Serialize(new - { - user_id = userId, - @params = paramsJson, - ts, - sign - }, JsonOptions), Encoding.UTF8, "application/json") - }; - - var response = await client.SendAsync(request); - if (!response.IsSuccessStatusCode) - { - _logger.LogError( - $"Response Error: {response.StatusCode}, {await response.Content.ReadAsStringAsync()}"); - return new List(); - } - - var result = await JsonSerializer.DeserializeAsync( - await response.Content.ReadAsStreamAsync(), JsonOptions); - - // Check if we have a valid response and orders in the list - if (result?.Data?.Orders != null && result.Data.Orders.Count != 0) return result.Data.Orders; - _logger.LogWarning($"No orders found with IDs: {orderIdsParam}"); - return []; - } - catch (Exception ex) - { - _logger.LogError(ex, $"Error fetching orders"); - throw; - } - } - - /// - /// Handle an incoming webhook from Afdian's payment platform - /// - /// The HTTP request containing webhook data - /// An action to process the received order - /// A WebhookResponse object to be returned to Afdian - public async Task HandleWebhook( - HttpRequest request, - Func? processOrderAction - ) - { - _logger.LogInformation("Received webhook request from afdian..."); - - try - { - // Read the request body - string requestBody; - using (var reader = new StreamReader(request.Body, Encoding.UTF8)) - { - requestBody = await reader.ReadToEndAsync(); - } - - if (string.IsNullOrEmpty(requestBody)) - { - _logger.LogError("Webhook request body is empty"); - return new WebhookResponse { ErrorCode = 400, ErrorMessage = "Empty request body" }; - } - - _logger.LogInformation($"Received webhook: {requestBody}"); - - // Parse the webhook data - var webhook = JsonSerializer.Deserialize(requestBody, JsonOptions); - - if (webhook == null) - { - _logger.LogError("Failed to parse webhook data"); - return new WebhookResponse { ErrorCode = 400, ErrorMessage = "Invalid webhook data" }; - } - - // Validate the webhook type - if (webhook.Data.Type != "order") - { - _logger.LogWarning($"Unsupported webhook type: {webhook.Data.Type}"); - return WebhookResponse.Success; - } - - // Process the order - try - { - // Check for duplicate order processing by storing processed order IDs - // (You would implement a more permanent storage mechanism for production) - if (processOrderAction != null) - await processOrderAction(webhook.Data); - else - _logger.LogInformation( - $"Order received but no processing action provided: {webhook.Data.Order.TradeNumber}"); - } - catch (Exception ex) - { - _logger.LogError(ex, $"Error processing order {webhook.Data.Order.TradeNumber}"); - // Still returning success to Afdian to prevent repeated callbacks - // Your system should handle the error internally - } - - // Return success response to Afdian - return WebhookResponse.Success; - } - catch (Exception ex) - { - _logger.LogError(ex, "Error handling webhook"); - return WebhookResponse.Success; - } - } - - public string? GetSubscriptionPlanId(string subscriptionKey) - { - var planId = _configuration[$"Payment:Subscriptions:Afdian:{subscriptionKey}"]; - - if (string.IsNullOrEmpty(planId)) - { - _logger.LogWarning($"Unknown subscription key: {subscriptionKey}"); - return null; - } - - return planId; - } -} - -public class OrderResponse -{ - [JsonPropertyName("ec")] public int ErrorCode { get; set; } - - [JsonPropertyName("em")] public string ErrorMessage { get; set; } = null!; - - [JsonPropertyName("data")] public OrderData Data { get; set; } = null!; -} - -public class OrderData -{ - [JsonPropertyName("list")] public List Orders { get; set; } = null!; - - [JsonPropertyName("total_count")] public int TotalCount { get; set; } - - [JsonPropertyName("total_page")] public int TotalPages { get; set; } - - [JsonPropertyName("request")] public RequestDetails Request { get; set; } = null!; -} - -public class OrderItem : ISubscriptionOrder -{ - [JsonPropertyName("out_trade_no")] public string TradeNumber { get; set; } = null!; - - [JsonPropertyName("user_id")] public string UserId { get; set; } = null!; - - [JsonPropertyName("plan_id")] public string PlanId { get; set; } = null!; - - [JsonPropertyName("month")] public int Months { get; set; } - - [JsonPropertyName("total_amount")] public string TotalAmount { get; set; } = null!; - - [JsonPropertyName("show_amount")] public string ShowAmount { get; set; } = null!; - - [JsonPropertyName("status")] public int Status { get; set; } - - [JsonPropertyName("remark")] public string Remark { get; set; } = null!; - - [JsonPropertyName("redeem_id")] public string RedeemId { get; set; } = null!; - - [JsonPropertyName("product_type")] public int ProductType { get; set; } - - [JsonPropertyName("discount")] public string Discount { get; set; } = null!; - - [JsonPropertyName("sku_detail")] public List SkuDetail { get; set; } = null!; - - [JsonPropertyName("create_time")] public long CreateTime { get; set; } - - [JsonPropertyName("user_name")] public string UserName { get; set; } = null!; - - [JsonPropertyName("plan_title")] public string PlanTitle { get; set; } = null!; - - [JsonPropertyName("user_private_id")] public string UserPrivateId { get; set; } = null!; - - [JsonPropertyName("address_person")] public string AddressPerson { get; set; } = null!; - - [JsonPropertyName("address_phone")] public string AddressPhone { get; set; } = null!; - - [JsonPropertyName("address_address")] public string AddressAddress { get; set; } = null!; - - public Instant BegunAt => Instant.FromUnixTimeSeconds(CreateTime); - - public Duration Duration => Duration.FromDays(Months * 30); - - public string Provider => "afdian"; - - public string Id => TradeNumber; - - public string SubscriptionId => PlanId; - - public string AccountId => UserId; -} - -public class RequestDetails -{ - [JsonPropertyName("user_id")] public string UserId { get; set; } = null!; - - [JsonPropertyName("params")] public string Params { get; set; } = null!; - - [JsonPropertyName("ts")] public long Timestamp { get; set; } - - [JsonPropertyName("sign")] public string Sign { get; set; } = null!; -} - -/// -/// Request structure for Afdian webhook -/// -public class WebhookRequest -{ - [JsonPropertyName("ec")] public int ErrorCode { get; set; } - - [JsonPropertyName("em")] public string ErrorMessage { get; set; } = null!; - - [JsonPropertyName("data")] public WebhookOrderData Data { get; set; } = null!; -} - -/// -/// Order data contained in the webhook -/// -public class WebhookOrderData -{ - [JsonPropertyName("type")] public string Type { get; set; } = null!; - - [JsonPropertyName("order")] public WebhookOrderDetails Order { get; set; } = null!; -} - -/// -/// Order details in the webhook -/// -public class WebhookOrderDetails : OrderItem -{ - [JsonPropertyName("custom_order_id")] public string CustomOrderId { get; set; } = null!; -} - -/// -/// Response structure to acknowledge webhook receipt -/// -public class WebhookResponse -{ - [JsonPropertyName("ec")] public int ErrorCode { get; set; } = 200; - - [JsonPropertyName("em")] public string ErrorMessage { get; set; } = ""; - - public static WebhookResponse Success => new() - { - ErrorCode = 200, - ErrorMessage = string.Empty - }; -} - -/// -/// SKU detail item -/// -public class SkuDetailItem -{ - [JsonPropertyName("sku_id")] public string SkuId { get; set; } = null!; - - [JsonPropertyName("count")] public int Count { get; set; } - - [JsonPropertyName("name")] public string Name { get; set; } = null!; - - [JsonPropertyName("album_id")] public string AlbumId { get; set; } = null!; - - [JsonPropertyName("pic")] public string Picture { get; set; } = null!; -} diff --git a/DysonNetwork.Sphere/Wallet/PaymentHandlers/ISubscriptionOrder.cs b/DysonNetwork.Sphere/Wallet/PaymentHandlers/ISubscriptionOrder.cs deleted file mode 100644 index a64aa11..0000000 --- a/DysonNetwork.Sphere/Wallet/PaymentHandlers/ISubscriptionOrder.cs +++ /dev/null @@ -1,18 +0,0 @@ -using NodaTime; - -namespace DysonNetwork.Sphere.Wallet.PaymentHandlers; - -public interface ISubscriptionOrder -{ - public string Id { get; } - - public string SubscriptionId { get; } - - public Instant BegunAt { get; } - - public Duration Duration { get; } - - public string Provider { get; } - - public string AccountId { get; } -} diff --git a/DysonNetwork.Sphere/Wallet/PaymentService.cs b/DysonNetwork.Sphere/Wallet/PaymentService.cs deleted file mode 100644 index 4f7ee69..0000000 --- a/DysonNetwork.Sphere/Wallet/PaymentService.cs +++ /dev/null @@ -1,297 +0,0 @@ -using System.Globalization; -using DysonNetwork.Sphere.Account; -using DysonNetwork.Sphere.Localization; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Storage; -using Microsoft.Extensions.Localization; -using NodaTime; - -namespace DysonNetwork.Sphere.Wallet; - -public class PaymentService( - AppDatabase db, - WalletService wat, - NotificationService nty, - IStringLocalizer localizer -) -{ - public async Task CreateOrderAsync( - Guid? payeeWalletId, - string currency, - decimal amount, - Duration? expiration = null, - string? appIdentifier = null, - Dictionary? meta = null, - bool reuseable = true - ) - { - // Check if there's an existing unpaid order that can be reused - if (reuseable && appIdentifier != null) - { - var existingOrder = await db.PaymentOrders - .Where(o => o.Status == OrderStatus.Unpaid && - o.PayeeWalletId == payeeWalletId && - o.Currency == currency && - o.Amount == amount && - o.AppIdentifier == appIdentifier && - o.ExpiredAt > SystemClock.Instance.GetCurrentInstant()) - .FirstOrDefaultAsync(); - - // If an existing order is found, check if meta matches - if (existingOrder != null && meta != null && existingOrder.Meta != null) - { - // Compare meta dictionaries - if they are equivalent, reuse the order - var metaMatches = existingOrder.Meta.Count == meta.Count && - !existingOrder.Meta.Except(meta).Any(); - - if (metaMatches) - { - return existingOrder; - } - } - } - - // Create a new order if no reusable order was found - var order = new Order - { - PayeeWalletId = payeeWalletId, - Currency = currency, - Amount = amount, - ExpiredAt = SystemClock.Instance.GetCurrentInstant().Plus(expiration ?? Duration.FromHours(24)), - AppIdentifier = appIdentifier, - Meta = meta - }; - - db.PaymentOrders.Add(order); - await db.SaveChangesAsync(); - return order; - } - - public async Task CreateTransactionWithAccountAsync( - Guid? payerAccountId, - Guid? payeeAccountId, - string currency, - decimal amount, - string? remarks = null, - TransactionType type = TransactionType.System - ) - { - Wallet? payer = null, payee = null; - if (payerAccountId.HasValue) - payer = await db.Wallets.FirstOrDefaultAsync(e => e.AccountId == payerAccountId.Value); - if (payeeAccountId.HasValue) - payee = await db.Wallets.FirstOrDefaultAsync(e => e.AccountId == payeeAccountId.Value); - - if (payer == null && payerAccountId.HasValue) - throw new ArgumentException("Payer account was specified, but wallet was not found"); - if (payee == null && payeeAccountId.HasValue) - throw new ArgumentException("Payee account was specified, but wallet was not found"); - - return await CreateTransactionAsync( - payer?.Id, - payee?.Id, - currency, - amount, - remarks, - type - ); - } - - public async Task CreateTransactionAsync( - Guid? payerWalletId, - Guid? payeeWalletId, - string currency, - decimal amount, - string? remarks = null, - TransactionType type = TransactionType.System - ) - { - if (payerWalletId == null && payeeWalletId == null) - throw new ArgumentException("At least one wallet must be specified."); - if (amount <= 0) throw new ArgumentException("Cannot create transaction with negative or zero amount."); - - var transaction = new Transaction - { - PayerWalletId = payerWalletId, - PayeeWalletId = payeeWalletId, - Currency = currency, - Amount = amount, - Remarks = remarks, - Type = type - }; - - if (payerWalletId.HasValue) - { - var (payerPocket, isNewlyCreated) = - await wat.GetOrCreateWalletPocketAsync(payerWalletId.Value, currency); - - if (isNewlyCreated || payerPocket.Amount < amount) - throw new InvalidOperationException("Insufficient funds"); - - await db.WalletPockets - .Where(p => p.Id == payerPocket.Id && p.Amount >= amount) - .ExecuteUpdateAsync(s => - s.SetProperty(p => p.Amount, p => p.Amount - amount)); - } - - if (payeeWalletId.HasValue) - { - var (payeePocket, isNewlyCreated) = - await wat.GetOrCreateWalletPocketAsync(payeeWalletId.Value, currency, amount); - - if (!isNewlyCreated) - await db.WalletPockets - .Where(p => p.Id == payeePocket.Id) - .ExecuteUpdateAsync(s => - s.SetProperty(p => p.Amount, p => p.Amount + amount)); - } - - db.PaymentTransactions.Add(transaction); - await db.SaveChangesAsync(); - return transaction; - } - - public async Task PayOrderAsync(Guid orderId, Guid payerWalletId) - { - var order = await db.PaymentOrders - .Include(o => o.Transaction) - .FirstOrDefaultAsync(o => o.Id == orderId); - - if (order == null) - { - throw new InvalidOperationException("Order not found"); - } - - if (order.Status != OrderStatus.Unpaid) - { - throw new InvalidOperationException($"Order is in invalid status: {order.Status}"); - } - - if (order.ExpiredAt < SystemClock.Instance.GetCurrentInstant()) - { - order.Status = OrderStatus.Expired; - await db.SaveChangesAsync(); - throw new InvalidOperationException("Order has expired"); - } - - var transaction = await CreateTransactionAsync( - payerWalletId, - order.PayeeWalletId, - order.Currency, - order.Amount, - order.Remarks ?? $"Payment for Order #{order.Id}", - type: TransactionType.Order); - - order.TransactionId = transaction.Id; - order.Transaction = transaction; - order.Status = OrderStatus.Paid; - - await db.SaveChangesAsync(); - - await NotifyOrderPaid(order); - - return order; - } - - private async Task NotifyOrderPaid(Order order) - { - if (order.PayeeWallet is null) return; - var account = await db.Accounts.FirstOrDefaultAsync(a => a.Id == order.PayeeWallet.AccountId); - if (account is null) return; - - AccountService.SetCultureInfo(account); - - // Due to ID is uuid, it longer than 8 words for sure - var readableOrderId = order.Id.ToString().Replace("-", "")[..8]; - var readableOrderRemark = order.Remarks ?? $"#{readableOrderId}"; - - await nty.SendNotification( - account, - "wallets.orders.paid", - localizer["OrderPaidTitle", $"#{readableOrderId}"], - null, - localizer["OrderPaidBody", order.Amount.ToString(CultureInfo.InvariantCulture), order.Currency, - readableOrderRemark], - new Dictionary() - { - ["order_id"] = order.Id.ToString() - } - ); - } - - public async Task CancelOrderAsync(Guid orderId) - { - var order = await db.PaymentOrders.FindAsync(orderId); - if (order == null) - { - throw new InvalidOperationException("Order not found"); - } - - if (order.Status != OrderStatus.Unpaid) - { - throw new InvalidOperationException($"Cannot cancel order in status: {order.Status}"); - } - - order.Status = OrderStatus.Cancelled; - await db.SaveChangesAsync(); - return order; - } - - public async Task<(Order Order, Transaction RefundTransaction)> RefundOrderAsync(Guid orderId) - { - var order = await db.PaymentOrders - .Include(o => o.Transaction) - .FirstOrDefaultAsync(o => o.Id == orderId); - - if (order == null) - { - throw new InvalidOperationException("Order not found"); - } - - if (order.Status != OrderStatus.Paid) - { - throw new InvalidOperationException($"Cannot refund order in status: {order.Status}"); - } - - if (order.Transaction == null) - { - throw new InvalidOperationException("Order has no associated transaction"); - } - - var refundTransaction = await CreateTransactionAsync( - order.PayeeWalletId, - order.Transaction.PayerWalletId, - order.Currency, - order.Amount, - $"Refund for order {order.Id}"); - - order.Status = OrderStatus.Finished; - await db.SaveChangesAsync(); - - return (order, refundTransaction); - } - - public async Task TransferAsync(Guid payerAccountId, Guid payeeAccountId, string currency, - decimal amount) - { - var payerWallet = await wat.GetWalletAsync(payerAccountId); - if (payerWallet == null) - { - throw new InvalidOperationException($"Payer wallet not found for account {payerAccountId}"); - } - - var payeeWallet = await wat.GetWalletAsync(payeeAccountId); - if (payeeWallet == null) - { - throw new InvalidOperationException($"Payee wallet not found for account {payeeAccountId}"); - } - - return await CreateTransactionAsync( - payerWallet.Id, - payeeWallet.Id, - currency, - amount, - $"Transfer from account {payerAccountId} to {payeeAccountId}", - TransactionType.Transfer); - } -} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Wallet/Subscription.cs b/DysonNetwork.Sphere/Wallet/Subscription.cs deleted file mode 100644 index d089f5c..0000000 --- a/DysonNetwork.Sphere/Wallet/Subscription.cs +++ /dev/null @@ -1,256 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; -using Microsoft.EntityFrameworkCore; -using NodaTime; - -namespace DysonNetwork.Sphere.Wallet; - -public record class SubscriptionTypeData( - string Identifier, - string? GroupIdentifier, - string Currency, - decimal BasePrice, - int? RequiredLevel = null -) -{ - public static readonly Dictionary SubscriptionDict = - new() - { - [SubscriptionType.Twinkle] = new SubscriptionTypeData( - SubscriptionType.Twinkle, - SubscriptionType.StellarProgram, - WalletCurrency.SourcePoint, - 0, - 1 - ), - [SubscriptionType.Stellar] = new SubscriptionTypeData( - SubscriptionType.Stellar, - SubscriptionType.StellarProgram, - WalletCurrency.SourcePoint, - 1200, - 3 - ), - [SubscriptionType.Nova] = new SubscriptionTypeData( - SubscriptionType.Nova, - SubscriptionType.StellarProgram, - WalletCurrency.SourcePoint, - 2400, - 6 - ), - [SubscriptionType.Supernova] = new SubscriptionTypeData( - SubscriptionType.Supernova, - SubscriptionType.StellarProgram, - WalletCurrency.SourcePoint, - 3600, - 9 - ) - }; - - public static readonly Dictionary SubscriptionHumanReadable = - new() - { - [SubscriptionType.Twinkle] = "Stellar Program Twinkle", - [SubscriptionType.Stellar] = "Stellar Program", - [SubscriptionType.Nova] = "Stellar Program Nova", - [SubscriptionType.Supernova] = "Stellar Program Supernova" - }; -} - -public abstract class SubscriptionType -{ - /// - /// DO NOT USE THIS TYPE DIRECTLY, - /// this is the prefix of all the stellar program subscriptions. - /// - public const string StellarProgram = "solian.stellar"; - - /// - /// No actual usage, just tells there is a free level named twinkle. - /// Applies to every registered user by default, so there is no need to create a record in db for that. - /// - public const string Twinkle = "solian.stellar.twinkle"; - - public const string Stellar = "solian.stellar.primary"; - public const string Nova = "solian.stellar.nova"; - public const string Supernova = "solian.stellar.supernova"; -} - -public abstract class SubscriptionPaymentMethod -{ - /// - /// The solar points / solar dollars. - /// - public const string InAppWallet = "solian.wallet"; - - /// - /// afdian.com - /// aka. China patreon - /// - public const string Afdian = "afdian"; -} - -public enum SubscriptionStatus -{ - Unpaid, - Active, - Expired, - Cancelled -} - -/// -/// The subscription is for the Stellar Program in most cases. -/// The paid subscription in another word. -/// -[Index(nameof(Identifier))] -public class Subscription : ModelBase -{ - public Guid Id { get; set; } = Guid.NewGuid(); - public Instant BegunAt { get; set; } - public Instant? EndedAt { get; set; } - - /// - /// The type of the subscriptions - /// - [MaxLength(4096)] - public string Identifier { get; set; } = null!; - - /// - /// The field is used to override the activation status of the membership. - /// Might be used for refund handling and other special cases. - /// - /// Go see the IsAvailable field if you want to get real the status of the membership. - /// - public bool IsActive { get; set; } = true; - - /// - /// Indicates is the current user got the membership for free, - /// to prevent giving the same discount for the same user again. - /// - public bool IsFreeTrial { get; set; } - - public SubscriptionStatus Status { get; set; } = SubscriptionStatus.Unpaid; - - [MaxLength(4096)] public string PaymentMethod { get; set; } = null!; - [Column(TypeName = "jsonb")] public PaymentDetails PaymentDetails { get; set; } = null!; - public decimal BasePrice { get; set; } - public Guid? CouponId { get; set; } - public Coupon? Coupon { get; set; } - public Instant? RenewalAt { get; set; } - - public Guid AccountId { get; set; } - public Account.Account Account { get; set; } = null!; - - [NotMapped] - public bool IsAvailable - { - get - { - if (!IsActive) return false; - - var now = SystemClock.Instance.GetCurrentInstant(); - - if (BegunAt > now) return false; - if (EndedAt.HasValue && now > EndedAt.Value) return false; - if (RenewalAt.HasValue && now > RenewalAt.Value) return false; - if (Status != SubscriptionStatus.Active) return false; - - return true; - } - } - - [NotMapped] - public decimal FinalPrice - { - get - { - if (IsFreeTrial) return 0; - if (Coupon == null) return BasePrice; - - var now = SystemClock.Instance.GetCurrentInstant(); - if (Coupon.AffectedAt.HasValue && now < Coupon.AffectedAt.Value || - Coupon.ExpiredAt.HasValue && now > Coupon.ExpiredAt.Value) return BasePrice; - - if (Coupon.DiscountAmount.HasValue) return BasePrice - Coupon.DiscountAmount.Value; - if (Coupon.DiscountRate.HasValue) return BasePrice * (decimal)(1 - Coupon.DiscountRate.Value); - return BasePrice; - } - } - - public SubscriptionReferenceObject ToReference() - { - return new SubscriptionReferenceObject - { - Id = Id, - Status = Status, - Identifier = Identifier, - IsActive = IsActive, - AccountId = AccountId, - CreatedAt = CreatedAt, - UpdatedAt = UpdatedAt, - DeletedAt = DeletedAt, - }; - } -} - -public class PaymentDetails -{ - public string Currency { get; set; } = null!; - public string? OrderId { get; set; } -} - -public class SubscriptionReferenceObject : ModelBase -{ - public Guid Id { get; set; } = Guid.NewGuid(); - public SubscriptionStatus Status { get; set; } - public string Identifier { get; set; } = null!; - public bool IsActive { get; set; } = true; - public Guid AccountId { get; set; } -} - -/// -/// A discount that can applies in purchases among the Solar Network. -/// For now, it can be used in the subscription purchase. -/// -public class Coupon : ModelBase -{ - public Guid Id { get; set; } = Guid.NewGuid(); - - /// - /// The items that can apply this coupon. - /// Leave it to null to apply to all items. - /// - [MaxLength(4096)] - public string? Identifier { get; set; } - - /// - /// The code that human-readable and memorizable. - /// Leave it blank to use it only with the ID. - /// - [MaxLength(1024)] - public string? Code { get; set; } - - public Instant? AffectedAt { get; set; } - public Instant? ExpiredAt { get; set; } - - /// - /// The amount of the discount. - /// If this field and the rate field are both not null, - /// the amount discount will be applied and the discount rate will be ignored. - /// Formula: final price = base price - discount amount - /// - public decimal? DiscountAmount { get; set; } - - /// - /// The percentage of the discount. - /// If this field and the amount field are both not null, - /// this field will be ignored. - /// Formula: final price = base price * (1 - discount rate) - /// - public double? DiscountRate { get; set; } - - /// - /// The max usage of the current coupon. - /// Leave it to null to use it unlimited. - /// - public int? MaxUsage { get; set; } -} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Wallet/SubscriptionController.cs b/DysonNetwork.Sphere/Wallet/SubscriptionController.cs deleted file mode 100644 index 9b8ca65..0000000 --- a/DysonNetwork.Sphere/Wallet/SubscriptionController.cs +++ /dev/null @@ -1,204 +0,0 @@ -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; -using NodaTime; -using System.ComponentModel.DataAnnotations; -using DysonNetwork.Sphere.Wallet.PaymentHandlers; - -namespace DysonNetwork.Sphere.Wallet; - -[ApiController] -[Route("/api/subscriptions")] -public class SubscriptionController(SubscriptionService subscriptions, AfdianPaymentHandler afdian, AppDatabase db) : ControllerBase -{ - [HttpGet] - [Authorize] - public async Task>> ListSubscriptions( - [FromQuery] int offset = 0, - [FromQuery] int take = 20 - ) - { - if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); - - var query = db.WalletSubscriptions.AsQueryable() - .Where(s => s.AccountId == currentUser.Id) - .Include(s => s.Coupon) - .OrderByDescending(s => s.BegunAt); - - var totalCount = await query.CountAsync(); - - var subscriptionsList = await query - .Skip(offset) - .Take(take) - .ToListAsync(); - - Response.Headers["X-Total"] = totalCount.ToString(); - - return subscriptionsList; - } - - [HttpGet("fuzzy/{prefix}")] - [Authorize] - public async Task> GetSubscriptionFuzzy(string prefix) - { - if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); - - var subs = await db.WalletSubscriptions - .Where(s => s.AccountId == currentUser.Id && s.IsActive) - .Where(s => EF.Functions.ILike(s.Identifier, prefix + "%")) - .OrderByDescending(s => s.BegunAt) - .ToListAsync(); - if (subs.Count == 0) return NotFound(); - var subscription = subs.FirstOrDefault(s => s.IsAvailable); - if (subscription is null) return NotFound(); - - return Ok(subscription); - } - - [HttpGet("{identifier}")] - [Authorize] - public async Task> GetSubscription(string identifier) - { - if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); - - var subscription = await subscriptions.GetSubscriptionAsync(currentUser.Id, identifier); - if (subscription is null) return NotFound($"Subscription with identifier {identifier} was not found."); - - return subscription; - } - - public class CreateSubscriptionRequest - { - [Required] public string Identifier { get; set; } = null!; - [Required] public string PaymentMethod { get; set; } = null!; - [Required] public PaymentDetails PaymentDetails { get; set; } = null!; - public string? Coupon { get; set; } - public int? CycleDurationDays { get; set; } - public bool IsFreeTrial { get; set; } = false; - public bool IsAutoRenewal { get; set; } = true; - } - - [HttpPost] - [Authorize] - public async Task> CreateSubscription( - [FromBody] CreateSubscriptionRequest request, - [FromHeader(Name = "X-Noop")] bool noop = false - ) - { - if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); - - Duration? cycleDuration = null; - if (request.CycleDurationDays.HasValue) - cycleDuration = Duration.FromDays(request.CycleDurationDays.Value); - - try - { - var subscription = await subscriptions.CreateSubscriptionAsync( - currentUser, - request.Identifier, - request.PaymentMethod, - request.PaymentDetails, - cycleDuration, - request.Coupon, - request.IsFreeTrial, - request.IsAutoRenewal, - noop - ); - - return subscription; - } - catch (ArgumentOutOfRangeException ex) - { - return BadRequest(ex.Message); - } - catch (InvalidOperationException ex) - { - return BadRequest(ex.Message); - } - } - - [HttpPost("{identifier}/cancel")] - [Authorize] - public async Task> CancelSubscription(string identifier) - { - if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); - - try - { - var subscription = await subscriptions.CancelSubscriptionAsync(currentUser.Id, identifier); - return subscription; - } - catch (InvalidOperationException ex) - { - return BadRequest(ex.Message); - } - } - - [HttpPost("{identifier}/order")] - [Authorize] - public async Task> CreateSubscriptionOrder(string identifier) - { - if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); - - try - { - var order = await subscriptions.CreateSubscriptionOrder(currentUser.Id, identifier); - return order; - } - catch (InvalidOperationException ex) - { - return BadRequest(ex.Message); - } - } - - public class SubscriptionOrderRequest - { - [Required] public Guid OrderId { get; set; } - } - - [HttpPost("order/handle")] - [Authorize] - public async Task> HandleSubscriptionOrder([FromBody] SubscriptionOrderRequest request) - { - var order = await db.PaymentOrders.FindAsync(request.OrderId); - if (order is null) return NotFound($"Order with ID {request.OrderId} was not found."); - - try - { - var subscription = await subscriptions.HandleSubscriptionOrder(order); - return subscription; - } - catch (InvalidOperationException ex) - { - return BadRequest(ex.Message); - } - } - - public class RestorePurchaseRequest - { - [Required] public string OrderId { get; set; } = null!; - } - - [HttpPost("order/restore/afdian")] - [Authorize] - public async Task RestorePurchaseFromAfdian([FromBody] RestorePurchaseRequest request) - { - var order = await afdian.GetOrderAsync(request.OrderId); - if (order is null) return NotFound($"Order with ID {request.OrderId} was not found."); - - var subscription = await subscriptions.CreateSubscriptionFromOrder(order); - return Ok(subscription); - } - - [HttpPost("order/handle/afdian")] - public async Task> AfdianWebhook() - { - var response = await afdian.HandleWebhook(Request, async webhookData => - { - var order = webhookData.Order; - await subscriptions.CreateSubscriptionFromOrder(order); - }); - - return Ok(response); - } -} diff --git a/DysonNetwork.Sphere/Wallet/SubscriptionRenewalJob.cs b/DysonNetwork.Sphere/Wallet/SubscriptionRenewalJob.cs deleted file mode 100644 index 116fe51..0000000 --- a/DysonNetwork.Sphere/Wallet/SubscriptionRenewalJob.cs +++ /dev/null @@ -1,197 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using NodaTime; -using Quartz; - -namespace DysonNetwork.Sphere.Wallet; - -public class SubscriptionRenewalJob( - AppDatabase db, - SubscriptionService subscriptionService, - PaymentService paymentService, - WalletService walletService, - ILogger logger -) : IJob -{ - public async Task Execute(IJobExecutionContext context) - { - logger.LogInformation("Starting subscription auto-renewal job..."); - - // First update expired subscriptions - var expiredCount = await subscriptionService.UpdateExpiredSubscriptionsAsync(); - logger.LogInformation("Updated {ExpiredCount} expired subscriptions", expiredCount); - - var now = SystemClock.Instance.GetCurrentInstant(); - const int batchSize = 100; // Process in smaller batches - var processedCount = 0; - var renewedCount = 0; - var failedCount = 0; - - // Find subscriptions that need renewal (due for renewal and are still active) - var subscriptionsToRenew = await db.WalletSubscriptions - .Where(s => s.RenewalAt.HasValue && s.RenewalAt.Value <= now) // Due for renewal - .Where(s => s.Status == SubscriptionStatus.Active) // Only paid subscriptions - .Where(s => s.IsActive) // Only active subscriptions - .Where(s => !s.IsFreeTrial) // Exclude free trials - .OrderBy(s => s.RenewalAt) // Process oldest first - .Take(batchSize) - .Include(s => s.Coupon) // Include coupon information - .ToListAsync(); - - var totalSubscriptions = subscriptionsToRenew.Count; - logger.LogInformation("Found {TotalSubscriptions} subscriptions due for renewal", totalSubscriptions); - - foreach (var subscription in subscriptionsToRenew) - { - try - { - processedCount++; - logger.LogDebug( - "Processing renewal for subscription {SubscriptionId} (Identifier: {Identifier}) for account {AccountId}", - subscription.Id, subscription.Identifier, subscription.AccountId); - - if (subscription.RenewalAt is null) - { - logger.LogWarning( - "Subscription {SubscriptionId} (Identifier: {Identifier}) has no renewal date or has been cancelled.", - subscription.Id, subscription.Identifier); - subscription.Status = SubscriptionStatus.Cancelled; - db.WalletSubscriptions.Update(subscription); - await db.SaveChangesAsync(); - continue; - } - - // Calculate next cycle duration based on current cycle - var currentCycle = subscription.EndedAt!.Value - subscription.BegunAt; - - // Create an order for the renewal payment - var order = await paymentService.CreateOrderAsync( - null, - WalletCurrency.GoldenPoint, - subscription.FinalPrice, - appIdentifier: SubscriptionService.SubscriptionOrderIdentifier, - meta: new Dictionary() - { - ["subscription_id"] = subscription.Id.ToString(), - ["subscription_identifier"] = subscription.Identifier, - ["is_renewal"] = true - } - ); - - // Try to process the payment automatically - if (subscription.PaymentMethod == SubscriptionPaymentMethod.InAppWallet) - { - try - { - var wallet = await walletService.GetWalletAsync(subscription.AccountId); - if (wallet is null) continue; - - // Process automatic payment from wallet - await paymentService.PayOrderAsync(order.Id, wallet.Id); - - // Update subscription details - subscription.BegunAt = subscription.EndedAt!.Value; - subscription.EndedAt = subscription.BegunAt.Plus(currentCycle); - subscription.RenewalAt = subscription.EndedAt; - - db.WalletSubscriptions.Update(subscription); - await db.SaveChangesAsync(); - - renewedCount++; - logger.LogInformation("Successfully renewed subscription {SubscriptionId}", subscription.Id); - } - catch (Exception ex) - { - // If auto-payment fails, mark for manual payment - logger.LogWarning(ex, "Failed to auto-renew subscription {SubscriptionId} with wallet payment", - subscription.Id); - failedCount++; - } - } - else - { - // For other payment methods, mark as pending payment - logger.LogInformation("Subscription {SubscriptionId} requires manual payment via {PaymentMethod}", - subscription.Id, subscription.PaymentMethod); - failedCount++; - } - } - catch (Exception ex) - { - logger.LogError(ex, "Error processing subscription {SubscriptionId}", subscription.Id); - failedCount++; - } - - // Log progress periodically - if (processedCount % 20 == 0 || processedCount == totalSubscriptions) - { - logger.LogInformation( - "Progress: processed {ProcessedCount}/{TotalSubscriptions} subscriptions, {RenewedCount} renewed, {FailedCount} failed", - processedCount, totalSubscriptions, renewedCount, failedCount); - } - } - - logger.LogInformation( - "Completed subscription renewal job. Processed: {ProcessedCount}, Renewed: {RenewedCount}, Failed: {FailedCount}", - processedCount, renewedCount, failedCount); - - logger.LogInformation("Validating user stellar memberships..."); - - // Get all account IDs with StellarMembership - var accountsWithMemberships = await db.AccountProfiles - .Where(a => a.StellarMembership != null) - .Select(a => new { a.Id, a.StellarMembership }) - .ToListAsync(); - - logger.LogInformation("Found {Count} accounts with stellar memberships to validate", - accountsWithMemberships.Count); - - if (accountsWithMemberships.Count == 0) - { - logger.LogInformation("No stellar memberships found to validate"); - return; - } - - // Get all subscription IDs from StellarMemberships - var memberships = accountsWithMemberships - .Where(a => a.StellarMembership != null) - .Select(a => a.StellarMembership) - .Distinct() - .ToList(); - var membershipIds = memberships.Select(m => m!.Id).ToList(); - - // Get all valid subscriptions in a single query - var validSubscriptions = await db.WalletSubscriptions - .Where(s => membershipIds.Contains(s.Id)) - .Where(s => s.IsActive) - .ToListAsync(); - var validSubscriptionsId = validSubscriptions - .Where(s => s.IsAvailable) - .Select(s => s.Id) - .ToList(); - - // Identify accounts that need updating (membership expired or not in validSubscriptions) - var accountIdsToUpdate = accountsWithMemberships - .Where(a => a.StellarMembership != null && !validSubscriptionsId.Contains(a.StellarMembership.Id)) - .Select(a => a.Id) - .ToList(); - - // Log the IDs that will be updated for debugging - logger.LogDebug("Accounts with expired or invalid memberships: {AccountIds}", - string.Join(", ", accountIdsToUpdate)); - - if (accountIdsToUpdate.Count == 0) - { - logger.LogInformation("No expired/invalid stellar memberships found"); - return; - } - - // Update all accounts in a single batch operation - var updatedCount = await db.AccountProfiles - .Where(a => accountIdsToUpdate.Contains(a.Id)) - .ExecuteUpdateAsync(s => s - .SetProperty(a => a.StellarMembership, p => null) - ); - - logger.LogInformation("Updated {Count} accounts with expired/invalid stellar memberships", updatedCount); - } -} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Wallet/SubscriptionService.cs b/DysonNetwork.Sphere/Wallet/SubscriptionService.cs deleted file mode 100644 index a46dcf6..0000000 --- a/DysonNetwork.Sphere/Wallet/SubscriptionService.cs +++ /dev/null @@ -1,401 +0,0 @@ -using System.Text.Json; -using DysonNetwork.Sphere.Account; -using DysonNetwork.Sphere.Localization; -using DysonNetwork.Sphere.Storage; -using DysonNetwork.Sphere.Wallet.PaymentHandlers; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Localization; -using NodaTime; - -namespace DysonNetwork.Sphere.Wallet; - -public class SubscriptionService( - AppDatabase db, - PaymentService payment, - AccountService accounts, - NotificationService nty, - IStringLocalizer localizer, - IConfiguration configuration, - ICacheService cache, - ILogger logger -) -{ - public async Task CreateSubscriptionAsync( - Account.Account account, - string identifier, - string paymentMethod, - PaymentDetails paymentDetails, - Duration? cycleDuration = null, - string? coupon = null, - bool isFreeTrial = false, - bool isAutoRenewal = true, - bool noop = false - ) - { - var subscriptionInfo = SubscriptionTypeData - .SubscriptionDict.TryGetValue(identifier, out var template) - ? template - : null; - if (subscriptionInfo is null) - throw new ArgumentOutOfRangeException(nameof(identifier), $@"Subscription {identifier} was not found."); - var subscriptionsInGroup = subscriptionInfo.GroupIdentifier is not null - ? SubscriptionTypeData.SubscriptionDict - .Where(s => s.Value.GroupIdentifier == subscriptionInfo.GroupIdentifier) - .Select(s => s.Value.Identifier) - .ToArray() - : [identifier]; - - cycleDuration ??= Duration.FromDays(30); - - var existingSubscription = await GetSubscriptionAsync(account.Id, subscriptionsInGroup); - if (existingSubscription is not null && !noop) - throw new InvalidOperationException($"Active subscription with identifier {identifier} already exists."); - if (existingSubscription is not null) - return existingSubscription; - - if (subscriptionInfo.RequiredLevel > 0) - { - var profile = await db.AccountProfiles - .Where(p => p.AccountId == account.Id) - .FirstOrDefaultAsync(); - if (profile is null) throw new InvalidOperationException("Account profile was not found."); - if (profile.Level < subscriptionInfo.RequiredLevel) - throw new InvalidOperationException( - $"Account level must be at least {subscriptionInfo.RequiredLevel} to subscribe to {identifier}." - ); - } - - if (isFreeTrial) - { - var prevFreeTrial = await db.WalletSubscriptions - .Where(s => s.AccountId == account.Id && s.Identifier == identifier && s.IsFreeTrial) - .FirstOrDefaultAsync(); - if (prevFreeTrial is not null) - throw new InvalidOperationException("Free trial already exists."); - } - - Coupon? couponData = null; - if (coupon is not null) - { - var inputCouponId = Guid.TryParse(coupon, out var parsedCouponId) ? parsedCouponId : Guid.Empty; - couponData = await db.WalletCoupons - .Where(c => (c.Id == inputCouponId) || (c.Identifier != null && c.Identifier == coupon)) - .FirstOrDefaultAsync(); - if (couponData is null) throw new InvalidOperationException($"Coupon {coupon} was not found."); - } - - var now = SystemClock.Instance.GetCurrentInstant(); - var subscription = new Subscription - { - BegunAt = now, - EndedAt = now.Plus(cycleDuration.Value), - Identifier = identifier, - IsActive = true, - IsFreeTrial = isFreeTrial, - Status = SubscriptionStatus.Unpaid, - PaymentMethod = paymentMethod, - PaymentDetails = paymentDetails, - BasePrice = subscriptionInfo.BasePrice, - CouponId = couponData?.Id, - Coupon = couponData, - RenewalAt = (isFreeTrial || !isAutoRenewal) ? null : now.Plus(cycleDuration.Value), - AccountId = account.Id, - }; - - db.WalletSubscriptions.Add(subscription); - await db.SaveChangesAsync(); - - return subscription; - } - - public async Task CreateSubscriptionFromOrder(ISubscriptionOrder order) - { - var cfgSection = configuration.GetSection("Payment:Subscriptions"); - var provider = order.Provider; - - var currency = "irl"; - var subscriptionIdentifier = order.SubscriptionId; - switch (provider) - { - case "afdian": - // Get the Afdian section first, then bind it to a dictionary - var afdianPlans = cfgSection.GetSection("Afdian").Get>(); - logger.LogInformation("Afdian plans configuration: {Plans}", JsonSerializer.Serialize(afdianPlans)); - if (afdianPlans != null && afdianPlans.TryGetValue(subscriptionIdentifier, out var planName)) - subscriptionIdentifier = planName; - currency = "cny"; - break; - } - - var subscriptionTemplate = SubscriptionTypeData - .SubscriptionDict.TryGetValue(subscriptionIdentifier, out var template) - ? template - : null; - if (subscriptionTemplate is null) - throw new ArgumentOutOfRangeException(nameof(subscriptionIdentifier), - $@"Subscription {subscriptionIdentifier} was not found."); - - Account.Account? account = null; - if (!string.IsNullOrEmpty(provider)) - account = await accounts.LookupAccountByConnection(order.AccountId, provider); - else if (Guid.TryParse(order.AccountId, out var accountId)) - account = await db.Accounts.FirstOrDefaultAsync(a => a.Id == accountId); - - if (account is null) - throw new InvalidOperationException($"Account was not found with identifier {order.AccountId}"); - - var cycleDuration = order.Duration; - - var existingSubscription = await GetSubscriptionAsync(account.Id, subscriptionIdentifier); - if (existingSubscription is not null && existingSubscription.PaymentMethod != provider) - throw new InvalidOperationException( - $"Active subscription with identifier {subscriptionIdentifier} already exists."); - if (existingSubscription?.PaymentDetails.OrderId == order.Id) - return existingSubscription; - if (existingSubscription is not null) - { - // Same provider, but different order, renew the subscription - existingSubscription.PaymentDetails.OrderId = order.Id; - existingSubscription.EndedAt = order.BegunAt.Plus(cycleDuration); - existingSubscription.RenewalAt = order.BegunAt.Plus(cycleDuration); - existingSubscription.Status = SubscriptionStatus.Active; - - db.Update(existingSubscription); - await db.SaveChangesAsync(); - - return existingSubscription; - } - - var subscription = new Subscription - { - BegunAt = order.BegunAt, - EndedAt = order.BegunAt.Plus(cycleDuration), - IsActive = true, - Status = SubscriptionStatus.Active, - Identifier = subscriptionIdentifier, - PaymentMethod = provider, - PaymentDetails = new PaymentDetails - { - Currency = currency, - OrderId = order.Id, - }, - BasePrice = subscriptionTemplate.BasePrice, - RenewalAt = order.BegunAt.Plus(cycleDuration), - AccountId = account.Id, - }; - - db.WalletSubscriptions.Add(subscription); - await db.SaveChangesAsync(); - - await NotifySubscriptionBegun(subscription); - - return subscription; - } - - /// - /// Cancel the renewal of the current activated subscription. - /// - /// The user who requested the action. - /// The subscription identifier - /// - /// The active subscription was not found - public async Task CancelSubscriptionAsync(Guid accountId, string identifier) - { - var subscription = await GetSubscriptionAsync(accountId, identifier); - if (subscription is null) - throw new InvalidOperationException($"Subscription with identifier {identifier} was not found."); - if (subscription.Status != SubscriptionStatus.Active) - throw new InvalidOperationException("Subscription is already cancelled."); - if (subscription.RenewalAt is null) - throw new InvalidOperationException("Subscription is no need to be cancelled."); - if (subscription.PaymentMethod != SubscriptionPaymentMethod.InAppWallet) - throw new InvalidOperationException( - "Only in-app wallet subscription can be cancelled. For other payment methods, please head to the payment provider." - ); - - subscription.RenewalAt = null; - - await db.SaveChangesAsync(); - - // Invalidate the cache for this subscription - var cacheKey = $"{SubscriptionCacheKeyPrefix}{accountId}:{identifier}"; - await cache.RemoveAsync(cacheKey); - - return subscription; - } - - public const string SubscriptionOrderIdentifier = "solian.subscription.order"; - - /// - /// Creates a subscription order for an unpaid or expired subscription. - /// If the subscription is active, it will extend its expiration date. - /// - /// The unique identifier for the account associated with the subscription. - /// The unique subscription identifier. - /// A task that represents the asynchronous operation. The task result contains the created subscription order. - /// Thrown when no matching unpaid or expired subscription is found. - public async Task CreateSubscriptionOrder(Guid accountId, string identifier) - { - var subscription = await db.WalletSubscriptions - .Where(s => s.AccountId == accountId && s.Identifier == identifier) - .Where(s => s.Status != SubscriptionStatus.Expired) - .Include(s => s.Coupon) - .OrderByDescending(s => s.BegunAt) - .FirstOrDefaultAsync(); - if (subscription is null) throw new InvalidOperationException("No matching subscription found."); - - var subscriptionInfo = SubscriptionTypeData.SubscriptionDict - .TryGetValue(subscription.Identifier, out var template) - ? template - : null; - if (subscriptionInfo is null) throw new InvalidOperationException("No matching subscription found."); - - return await payment.CreateOrderAsync( - null, - subscriptionInfo.Currency, - subscription.FinalPrice, - appIdentifier: SubscriptionOrderIdentifier, - meta: new Dictionary() - { - ["subscription_id"] = subscription.Id.ToString(), - ["subscription_identifier"] = subscription.Identifier, - } - ); - } - - public async Task HandleSubscriptionOrder(Order order) - { - if (order.AppIdentifier != SubscriptionOrderIdentifier || order.Status != OrderStatus.Paid || - order.Meta?["subscription_id"] is not JsonElement subscriptionIdJson) - throw new InvalidOperationException("Invalid order."); - - var subscriptionId = Guid.TryParse(subscriptionIdJson.ToString(), out var parsedSubscriptionId) - ? parsedSubscriptionId - : Guid.Empty; - if (subscriptionId == Guid.Empty) - throw new InvalidOperationException("Invalid order."); - var subscription = await db.WalletSubscriptions - .Where(s => s.Id == subscriptionId) - .Include(s => s.Coupon) - .FirstOrDefaultAsync(); - if (subscription is null) - throw new InvalidOperationException("Invalid order."); - - if (subscription.Status == SubscriptionStatus.Expired) - { - var now = SystemClock.Instance.GetCurrentInstant(); - var cycle = subscription.BegunAt.Minus(subscription.RenewalAt ?? subscription.EndedAt ?? now); - - var nextRenewalAt = subscription.RenewalAt?.Plus(cycle); - var nextEndedAt = subscription.EndedAt?.Plus(cycle); - - subscription.RenewalAt = nextRenewalAt; - subscription.EndedAt = nextEndedAt; - } - - subscription.Status = SubscriptionStatus.Active; - - db.Update(subscription); - await db.SaveChangesAsync(); - - if (subscription.Identifier.StartsWith(SubscriptionType.StellarProgram)) - { - await db.AccountProfiles - .Where(a => a.AccountId == subscription.AccountId) - .ExecuteUpdateAsync(s => s.SetProperty(a => a.StellarMembership, subscription.ToReference())); - } - - await NotifySubscriptionBegun(subscription); - - return subscription; - } - - /// - /// Updates the status of expired subscriptions to reflect their current state. - /// This helps maintain accurate subscription records and is typically called periodically. - /// - /// Maximum number of subscriptions to process - /// Number of subscriptions that were marked as expired - public async Task UpdateExpiredSubscriptionsAsync(int batchSize = 100) - { - var now = SystemClock.Instance.GetCurrentInstant(); - - // Find active subscriptions that have passed their end date - var expiredSubscriptions = await db.WalletSubscriptions - .Where(s => s.IsActive) - .Where(s => s.Status == SubscriptionStatus.Active) - .Where(s => s.EndedAt.HasValue && s.EndedAt.Value < now) - .Take(batchSize) - .ToListAsync(); - - if (expiredSubscriptions.Count == 0) - return 0; - - foreach (var subscription in expiredSubscriptions) - { - subscription.Status = SubscriptionStatus.Expired; - - // Clear the cache for this subscription - var cacheKey = $"{SubscriptionCacheKeyPrefix}{subscription.AccountId}:{subscription.Identifier}"; - await cache.RemoveAsync(cacheKey); - } - - await db.SaveChangesAsync(); - return expiredSubscriptions.Count; - } - - private async Task NotifySubscriptionBegun(Subscription subscription) - { - var account = await db.Accounts.FirstOrDefaultAsync(a => a.Id == subscription.AccountId); - if (account is null) return; - - AccountService.SetCultureInfo(account); - - var humanReadableName = - SubscriptionTypeData.SubscriptionHumanReadable.TryGetValue(subscription.Identifier, out var humanReadable) - ? humanReadable - : subscription.Identifier; - var duration = subscription.EndedAt is not null - ? subscription.EndedAt.Value.Minus(subscription.BegunAt).Days.ToString() - : "infinite"; - - await nty.SendNotification( - account, - "subscriptions.begun", - localizer["SubscriptionAppliedTitle", humanReadableName], - null, - localizer["SubscriptionAppliedBody", duration, humanReadableName], - new Dictionary() - { - ["subscription_id"] = subscription.Id.ToString(), - } - ); - } - - private const string SubscriptionCacheKeyPrefix = "subscription:"; - - public async Task GetSubscriptionAsync(Guid accountId, params string[] identifiers) - { - // Create a unique cache key for this subscription - var cacheKey = $"{SubscriptionCacheKeyPrefix}{accountId}:{string.Join(",", identifiers)}"; - - // Try to get the subscription from cache first - var (found, cachedSubscription) = await cache.GetAsyncWithStatus(cacheKey); - if (found && cachedSubscription != null) - { - return cachedSubscription; - } - - // If not in cache, get from database - var subscription = await db.WalletSubscriptions - .Where(s => s.AccountId == accountId && identifiers.Contains(s.Identifier)) - .OrderByDescending(s => s.BegunAt) - .FirstOrDefaultAsync(); - - // Cache the result if found (with 30 minutes expiry) - if (subscription != null) - await cache.SetAsync(cacheKey, subscription, TimeSpan.FromMinutes(30)); - - return subscription; - } -} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Wallet/Wallet.cs b/DysonNetwork.Sphere/Wallet/Wallet.cs deleted file mode 100644 index e69d251..0000000 --- a/DysonNetwork.Sphere/Wallet/Wallet.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using System.Text.Json.Serialization; - -namespace DysonNetwork.Sphere.Wallet; - -public class Wallet : ModelBase -{ - public Guid Id { get; set; } = Guid.NewGuid(); - - public ICollection Pockets { get; set; } = new List(); - - public Guid AccountId { get; set; } - public Account.Account Account { get; set; } = null!; -} - -public class WalletPocket : ModelBase -{ - public Guid Id { get; set; } = Guid.NewGuid(); - [MaxLength(128)] public string Currency { get; set; } = null!; - public decimal Amount { get; set; } - - public Guid WalletId { get; set; } - [JsonIgnore] public Wallet Wallet { get; set; } = null!; -} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Wallet/WalletController.cs b/DysonNetwork.Sphere/Wallet/WalletController.cs deleted file mode 100644 index 6b256b9..0000000 --- a/DysonNetwork.Sphere/Wallet/WalletController.cs +++ /dev/null @@ -1,101 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using DysonNetwork.Sphere.Permission; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; - -namespace DysonNetwork.Sphere.Wallet; - -[ApiController] -[Route("/api/wallets")] -public class WalletController(AppDatabase db, WalletService ws, PaymentService payment) : ControllerBase -{ - [HttpPost] - [Authorize] - public async Task> CreateWallet() - { - if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); - - try - { - var wallet = await ws.CreateWalletAsync(currentUser.Id); - return Ok(wallet); - } - catch (Exception err) - { - return BadRequest(err.Message); - } - } - - [HttpGet] - [Authorize] - public async Task> GetWallet() - { - if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); - - var wallet = await ws.GetWalletAsync(currentUser.Id); - if (wallet is null) return NotFound("Wallet was not found, please create one first."); - return Ok(wallet); - } - - [HttpGet("transactions")] - [Authorize] - public async Task>> GetTransactions( - [FromQuery] int offset = 0, [FromQuery] int take = 20 - ) - { - if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); - - var query = db.PaymentTransactions.AsQueryable() - .Include(t => t.PayeeWallet) - .Include(t => t.PayerWallet) - .Where(t => (t.PayeeWallet != null && t.PayeeWallet.AccountId == currentUser.Id) || - (t.PayerWallet != null && t.PayerWallet.AccountId == currentUser.Id)); - - var transactionCount = await query.CountAsync(); - var transactions = await query - .Skip(offset) - .Take(take) - .OrderByDescending(t => t.CreatedAt) - .ToListAsync(); - - Response.Headers["X-Total"] = transactionCount.ToString(); - - return Ok(transactions); - } - - public class WalletBalanceRequest - { - public string? Remark { get; set; } - [Required] public decimal Amount { get; set; } - [Required] public string Currency { get; set; } = null!; - [Required] public Guid AccountId { get; set; } - } - - [HttpPost("balance")] - [Authorize] - [RequiredPermission("maintenance", "wallets.balance.modify")] - public async Task> ModifyWalletBalance([FromBody] WalletBalanceRequest request) - { - var wallet = await ws.GetWalletAsync(request.AccountId); - if (wallet is null) return NotFound("Wallet was not found."); - - var transaction = request.Amount >= 0 - ? await payment.CreateTransactionAsync( - payerWalletId: null, - payeeWalletId: wallet.Id, - currency: request.Currency, - amount: request.Amount, - remarks: request.Remark - ) - : await payment.CreateTransactionAsync( - payerWalletId: wallet.Id, - payeeWalletId: null, - currency: request.Currency, - amount: request.Amount, - remarks: request.Remark - ); - - return Ok(transaction); - } -} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Wallet/WalletService.cs b/DysonNetwork.Sphere/Wallet/WalletService.cs deleted file mode 100644 index 4d4d086..0000000 --- a/DysonNetwork.Sphere/Wallet/WalletService.cs +++ /dev/null @@ -1,49 +0,0 @@ -using Microsoft.EntityFrameworkCore; - -namespace DysonNetwork.Sphere.Wallet; - -public class WalletService(AppDatabase db) -{ - public async Task GetWalletAsync(Guid accountId) - { - return await db.Wallets - .Include(w => w.Pockets) - .FirstOrDefaultAsync(w => w.AccountId == accountId); - } - - public async Task CreateWalletAsync(Guid accountId) - { - var existingWallet = await db.Wallets.FirstOrDefaultAsync(w => w.AccountId == accountId); - if (existingWallet != null) - { - throw new InvalidOperationException($"Wallet already exists for account {accountId}"); - } - - var wallet = new Wallet { AccountId = accountId }; - - db.Wallets.Add(wallet); - await db.SaveChangesAsync(); - - return wallet; - } - - public async Task<(WalletPocket wallet, bool isNewlyCreated)> GetOrCreateWalletPocketAsync( - Guid walletId, - string currency, - decimal? initialAmount = null - ) - { - var pocket = await db.WalletPockets.FirstOrDefaultAsync(p => p.Currency == currency && p.WalletId == walletId); - if (pocket != null) return (pocket, false); - - pocket = new WalletPocket - { - Currency = currency, - Amount = initialAmount ?? 0, - WalletId = walletId - }; - - db.WalletPockets.Add(pocket); - return (pocket, true); - } -} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Connection/WebReader/EmbeddableBase.cs b/DysonNetwork.Sphere/WebReader/EmbeddableBase.cs similarity index 95% rename from DysonNetwork.Sphere/Connection/WebReader/EmbeddableBase.cs rename to DysonNetwork.Sphere/WebReader/EmbeddableBase.cs index afca49a..d1a499a 100644 --- a/DysonNetwork.Sphere/Connection/WebReader/EmbeddableBase.cs +++ b/DysonNetwork.Sphere/WebReader/EmbeddableBase.cs @@ -1,7 +1,7 @@ using System.Reflection; using System.Text.Json.Serialization; -namespace DysonNetwork.Sphere.Connection.WebReader; +namespace DysonNetwork.Sphere.WebReader; /// /// The embeddable can be used in the post or messages' meta's embeds fields diff --git a/DysonNetwork.Sphere/Connection/WebReader/LinkEmbed.cs b/DysonNetwork.Sphere/WebReader/LinkEmbed.cs similarity index 96% rename from DysonNetwork.Sphere/Connection/WebReader/LinkEmbed.cs rename to DysonNetwork.Sphere/WebReader/LinkEmbed.cs index ab2131f..b83cdcc 100644 --- a/DysonNetwork.Sphere/Connection/WebReader/LinkEmbed.cs +++ b/DysonNetwork.Sphere/WebReader/LinkEmbed.cs @@ -1,4 +1,4 @@ -namespace DysonNetwork.Sphere.Connection.WebReader; +namespace DysonNetwork.Sphere.WebReader; /// /// The link embed is a part of the embeddable implementations diff --git a/DysonNetwork.Sphere/Connection/WebReader/ScrapedArticle.cs b/DysonNetwork.Sphere/WebReader/ScrapedArticle.cs similarity index 70% rename from DysonNetwork.Sphere/Connection/WebReader/ScrapedArticle.cs rename to DysonNetwork.Sphere/WebReader/ScrapedArticle.cs index 6c39028..2c724e3 100644 --- a/DysonNetwork.Sphere/Connection/WebReader/ScrapedArticle.cs +++ b/DysonNetwork.Sphere/WebReader/ScrapedArticle.cs @@ -1,4 +1,4 @@ -namespace DysonNetwork.Sphere.Connection.WebReader; +namespace DysonNetwork.Sphere.WebReader; public class ScrapedArticle { diff --git a/DysonNetwork.Sphere/Connection/WebReader/WebArticle.cs b/DysonNetwork.Sphere/WebReader/WebArticle.cs similarity index 96% rename from DysonNetwork.Sphere/Connection/WebReader/WebArticle.cs rename to DysonNetwork.Sphere/WebReader/WebArticle.cs index 09b4291..fb06ed7 100644 --- a/DysonNetwork.Sphere/Connection/WebReader/WebArticle.cs +++ b/DysonNetwork.Sphere/WebReader/WebArticle.cs @@ -2,7 +2,7 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using System.Text.Json.Serialization; -namespace DysonNetwork.Sphere.Connection.WebReader; +namespace DysonNetwork.Sphere.WebReader; public class WebArticle : ModelBase { diff --git a/DysonNetwork.Sphere/Connection/WebReader/WebArticleController.cs b/DysonNetwork.Sphere/WebReader/WebArticleController.cs similarity index 97% rename from DysonNetwork.Sphere/Connection/WebReader/WebArticleController.cs rename to DysonNetwork.Sphere/WebReader/WebArticleController.cs index 96b73b6..05f4d50 100644 --- a/DysonNetwork.Sphere/Connection/WebReader/WebArticleController.cs +++ b/DysonNetwork.Sphere/WebReader/WebArticleController.cs @@ -1,7 +1,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; -namespace DysonNetwork.Sphere.Connection.WebReader; +namespace DysonNetwork.Sphere.WebReader; [ApiController] [Route("/api/feeds/articles")] diff --git a/DysonNetwork.Sphere/Connection/WebReader/WebFeedController.cs b/DysonNetwork.Sphere/WebReader/WebFeedController.cs similarity index 78% rename from DysonNetwork.Sphere/Connection/WebReader/WebFeedController.cs rename to DysonNetwork.Sphere/WebReader/WebFeedController.cs index 4001447..047e7fa 100644 --- a/DysonNetwork.Sphere/Connection/WebReader/WebFeedController.cs +++ b/DysonNetwork.Sphere/WebReader/WebFeedController.cs @@ -1,9 +1,10 @@ using System.ComponentModel.DataAnnotations; +using DysonNetwork.Shared.Proto; using DysonNetwork.Sphere.Publisher; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -namespace DysonNetwork.Sphere.Connection.WebReader; +namespace DysonNetwork.Sphere.WebReader; [Authorize] [ApiController] @@ -43,7 +44,7 @@ public class WebFeedController(WebFeedService webFeed, PublisherService ps) : Co [Authorize] public async Task CreateWebFeed([FromRoute] string pubName, [FromBody] WebFeedRequest request) { - if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); if (string.IsNullOrWhiteSpace(request.Url) || string.IsNullOrWhiteSpace(request.Title)) return BadRequest("Url and title are required"); @@ -51,7 +52,8 @@ public class WebFeedController(WebFeedService webFeed, PublisherService ps) : Co var publisher = await ps.GetPublisherByName(pubName); if (publisher is null) return NotFound(); - if (!await ps.IsMemberWithRole(publisher.Id, currentUser.Id, PublisherMemberRole.Editor)) + var accountId = Guid.Parse(currentUser.Id); + if (!await ps.IsMemberWithRole(publisher.Id, accountId, PublisherMemberRole.Editor)) return StatusCode(403, "You must be an editor of the publisher to create a web feed"); var feed = await webFeed.CreateWebFeedAsync(publisher, request); @@ -62,12 +64,13 @@ public class WebFeedController(WebFeedService webFeed, PublisherService ps) : Co [Authorize] public async Task UpdateFeed([FromRoute] string pubName, Guid id, [FromBody] WebFeedRequest request) { - if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); var publisher = await ps.GetPublisherByName(pubName); if (publisher is null) return NotFound(); - if (!await ps.IsMemberWithRole(publisher.Id, currentUser.Id, PublisherMemberRole.Editor)) + var accountId = Guid.Parse(currentUser.Id); + if (!await ps.IsMemberWithRole(publisher.Id, accountId, PublisherMemberRole.Editor)) return StatusCode(403, "You must be an editor of the publisher to update a web feed"); var feed = await webFeed.GetFeedAsync(id, publisherId: publisher.Id); @@ -82,12 +85,13 @@ public class WebFeedController(WebFeedService webFeed, PublisherService ps) : Co [Authorize] public async Task DeleteFeed([FromRoute] string pubName, Guid id) { - if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); var publisher = await ps.GetPublisherByName(pubName); if (publisher is null) return NotFound(); - if (!await ps.IsMemberWithRole(publisher.Id, currentUser.Id, PublisherMemberRole.Editor)) + var accountId = Guid.Parse(currentUser.Id); + if (!await ps.IsMemberWithRole(publisher.Id, accountId, PublisherMemberRole.Editor)) return StatusCode(403, "You must be an editor of the publisher to delete a web feed"); var feed = await webFeed.GetFeedAsync(id, publisherId: publisher.Id); @@ -104,12 +108,13 @@ public class WebFeedController(WebFeedService webFeed, PublisherService ps) : Co [Authorize] public async Task Scrap([FromRoute] string pubName, Guid id) { - if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); var publisher = await ps.GetPublisherByName(pubName); if (publisher is null) return NotFound(); - if (!await ps.IsMemberWithRole(publisher.Id, currentUser.Id, PublisherMemberRole.Editor)) + var accountId = Guid.Parse(currentUser.Id); + if (!await ps.IsMemberWithRole(publisher.Id, accountId, PublisherMemberRole.Editor)) return StatusCode(403, "You must be an editor of the publisher to scrape a web feed"); var feed = await webFeed.GetFeedAsync(id, publisherId: publisher.Id); diff --git a/DysonNetwork.Sphere/Connection/WebReader/WebFeedScraperJob.cs b/DysonNetwork.Sphere/WebReader/WebFeedScraperJob.cs similarity index 94% rename from DysonNetwork.Sphere/Connection/WebReader/WebFeedScraperJob.cs rename to DysonNetwork.Sphere/WebReader/WebFeedScraperJob.cs index fb9bc61..753d0c4 100644 --- a/DysonNetwork.Sphere/Connection/WebReader/WebFeedScraperJob.cs +++ b/DysonNetwork.Sphere/WebReader/WebFeedScraperJob.cs @@ -2,7 +2,7 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using Quartz; -namespace DysonNetwork.Sphere.Connection.WebReader; +namespace DysonNetwork.Sphere.WebReader; [DisallowConcurrentExecution] public class WebFeedScraperJob( diff --git a/DysonNetwork.Sphere/Connection/WebReader/WebFeedService.cs b/DysonNetwork.Sphere/WebReader/WebFeedService.cs similarity index 98% rename from DysonNetwork.Sphere/Connection/WebReader/WebFeedService.cs rename to DysonNetwork.Sphere/WebReader/WebFeedService.cs index fe8273b..8ced368 100644 --- a/DysonNetwork.Sphere/Connection/WebReader/WebFeedService.cs +++ b/DysonNetwork.Sphere/WebReader/WebFeedService.cs @@ -2,7 +2,7 @@ using System.ServiceModel.Syndication; using System.Xml; using Microsoft.EntityFrameworkCore; -namespace DysonNetwork.Sphere.Connection.WebReader; +namespace DysonNetwork.Sphere.WebReader; public class WebFeedService( AppDatabase database, diff --git a/DysonNetwork.Sphere/Connection/WebReader/WebReaderController.cs b/DysonNetwork.Sphere/WebReader/WebReaderController.cs similarity index 98% rename from DysonNetwork.Sphere/Connection/WebReader/WebReaderController.cs rename to DysonNetwork.Sphere/WebReader/WebReaderController.cs index 013e790..c400d0e 100644 --- a/DysonNetwork.Sphere/Connection/WebReader/WebReaderController.cs +++ b/DysonNetwork.Sphere/WebReader/WebReaderController.cs @@ -3,7 +3,7 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.RateLimiting; -namespace DysonNetwork.Sphere.Connection.WebReader; +namespace DysonNetwork.Sphere.WebReader; /// /// Controller for web scraping and link preview services diff --git a/DysonNetwork.Sphere/Connection/WebReader/WebReaderException.cs b/DysonNetwork.Sphere/WebReader/WebReaderException.cs similarity index 87% rename from DysonNetwork.Sphere/Connection/WebReader/WebReaderException.cs rename to DysonNetwork.Sphere/WebReader/WebReaderException.cs index 31b8032..651ac54 100644 --- a/DysonNetwork.Sphere/Connection/WebReader/WebReaderException.cs +++ b/DysonNetwork.Sphere/WebReader/WebReaderException.cs @@ -1,6 +1,6 @@ using System; -namespace DysonNetwork.Sphere.Connection.WebReader; +namespace DysonNetwork.Sphere.WebReader; /// /// Exception thrown when an error occurs during web reading operations diff --git a/DysonNetwork.Sphere/Connection/WebReader/WebReaderService.cs b/DysonNetwork.Sphere/WebReader/WebReaderService.cs similarity index 99% rename from DysonNetwork.Sphere/Connection/WebReader/WebReaderService.cs rename to DysonNetwork.Sphere/WebReader/WebReaderService.cs index d7f9bda..3992be7 100644 --- a/DysonNetwork.Sphere/Connection/WebReader/WebReaderService.cs +++ b/DysonNetwork.Sphere/WebReader/WebReaderService.cs @@ -1,10 +1,10 @@ using System.Globalization; using AngleSharp; using AngleSharp.Dom; -using DysonNetwork.Sphere.Storage; +using DysonNetwork.Shared.Cache; using HtmlAgilityPack; -namespace DysonNetwork.Sphere.Connection.WebReader; +namespace DysonNetwork.Sphere.WebReader; /// /// The service is amin to providing scrapping service to the Solar Network. @@ -13,7 +13,8 @@ namespace DysonNetwork.Sphere.Connection.WebReader; public class WebReaderService( IHttpClientFactory httpClientFactory, ILogger logger, - ICacheService cache) + ICacheService cache +) { private const string LinkPreviewCachePrefix = "scrap:preview:"; private const string LinkPreviewCacheGroup = "scrap:preview"; @@ -38,6 +39,7 @@ public class WebReaderService( logger.LogWarning("Failed to scrap article content for URL: {Url}", url); return null; } + var html = await response.Content.ReadAsStringAsync(cancellationToken); var doc = new HtmlDocument(); doc.LoadHtml(html); diff --git a/DysonNetwork.sln.DotSettings.user b/DysonNetwork.sln.DotSettings.user index c388d6b..04f33c9 100644 --- a/DysonNetwork.sln.DotSettings.user +++ b/DysonNetwork.sln.DotSettings.user @@ -71,6 +71,7 @@ ForceIncluded ForceIncluded ForceIncluded + ForceIncluded ForceIncluded ForceIncluded ForceIncluded From a03b8d1cac8d7964aa30deeabb60a787c7cb65d5 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Mon, 14 Jul 2025 22:36:59 +0800 Subject: [PATCH 18/42] :sparkles: Action log service grpc --- .../Account/AccountServiceGrpc.cs | 266 ------------------ DysonNetwork.Pass/Account/ActionLog.cs | 24 ++ DysonNetwork.Pass/Account/ActionLogService.cs | 2 +- .../Account/ActionLogServiceGrpc.cs | 114 ++++++++ .../Startup/ApplicationConfiguration.cs | 1 + DysonNetwork.Shared/Proto/account.proto | 48 ++++ DysonNetwork.Sphere/Post/PostController.cs | 10 +- 7 files changed, 191 insertions(+), 274 deletions(-) create mode 100644 DysonNetwork.Pass/Account/ActionLogServiceGrpc.cs diff --git a/DysonNetwork.Pass/Account/AccountServiceGrpc.cs b/DysonNetwork.Pass/Account/AccountServiceGrpc.cs index c94ae1d..1642ed4 100644 --- a/DysonNetwork.Pass/Account/AccountServiceGrpc.cs +++ b/DysonNetwork.Pass/Account/AccountServiceGrpc.cs @@ -56,68 +56,6 @@ public class AccountServiceGrpc( return response; } - public override async Task CreateAccount(CreateAccountRequest request, - ServerCallContext context) - { - // Map protobuf request to domain model - var account = new Account - { - Name = request.Name, - Nick = request.Nick, - Language = request.Language, - IsSuperuser = request.IsSuperuser, - ActivatedAt = request.Profile != null ? null : _clock.GetCurrentInstant(), - Profile = new AccountProfile - { - FirstName = request.Profile?.FirstName, - LastName = request.Profile?.LastName, - // Initialize other profile fields as needed - } - }; - - // Add to database - _db.Accounts.Add(account); - await _db.SaveChangesAsync(); - - _logger.LogInformation("Created new account with ID {AccountId}", account.Id); - return account.ToProtoValue(); - } - - public override async Task UpdateAccount(UpdateAccountRequest request, - ServerCallContext context) - { - if (!Guid.TryParse(request.Id, out var accountId)) - throw new RpcException(new Grpc.Core.Status(StatusCode.InvalidArgument, "Invalid account ID format")); - - var account = await _db.Accounts.FindAsync(accountId); - if (account == null) - throw new RpcException(new Grpc.Core.Status(StatusCode.NotFound, $"Account {request.Id} not found")); - - // Update fields if they are provided in the request - if (request.Name != null) account.Name = request.Name; - if (request.Nick != null) account.Nick = request.Nick; - if (request.Language != null) account.Language = request.Language; - if (request.IsSuperuser != null) account.IsSuperuser = request.IsSuperuser.Value; - - await _db.SaveChangesAsync(); - return account.ToProtoValue(); - } - - public override async Task DeleteAccount(DeleteAccountRequest request, ServerCallContext context) - { - if (!Guid.TryParse(request.Id, out var accountId)) - throw new RpcException(new Grpc.Core.Status(StatusCode.InvalidArgument, "Invalid account ID format")); - - var account = await _db.Accounts.FindAsync(accountId); - if (account == null) - throw new RpcException(new Grpc.Core.Status(StatusCode.NotFound, $"Account {request.Id} not found")); - - _db.Accounts.Remove(account); - - await _db.SaveChangesAsync(); - return new Empty(); - } - public override async Task ListAccounts(ListAccountsRequest request, ServerCallContext context) { @@ -161,210 +99,6 @@ public class AccountServiceGrpc( return response; } -// Implement other service methods following the same pattern... - -// Profile operations - public override async Task GetProfile(GetProfileRequest request, - ServerCallContext context) - { - if (!Guid.TryParse(request.AccountId, out var accountId)) - throw new RpcException(new Grpc.Core.Status(StatusCode.InvalidArgument, "Invalid account ID format")); - - var profile = await _db.AccountProfiles - .AsNoTracking() - .FirstOrDefaultAsync(p => p.AccountId == accountId); - - if (profile == null) - throw new RpcException(new Grpc.Core.Status(StatusCode.NotFound, - $"Profile for account {request.AccountId} not found")); - - return profile.ToProtoValue(); - } - - public override async Task UpdateProfile(UpdateProfileRequest request, - ServerCallContext context) - { - if (!Guid.TryParse(request.AccountId, out var accountId)) - throw new RpcException(new Grpc.Core.Status(StatusCode.InvalidArgument, "Invalid account ID format")); - - var profile = await _db.AccountProfiles - .FirstOrDefaultAsync(p => p.AccountId == accountId); - - if (profile == null) - throw new RpcException(new Grpc.Core.Status(StatusCode.NotFound, - $"Profile for account {request.AccountId} not found")); - - // Update only the fields specified in the field mask - if (request.UpdateMask == null || request.UpdateMask.Paths.Contains("first_name")) - profile.FirstName = request.Profile.FirstName; - - if (request.UpdateMask == null || request.UpdateMask.Paths.Contains("last_name")) - profile.LastName = request.Profile.LastName; - - // Update other fields similarly... - - await _db.SaveChangesAsync(); - return profile.ToProtoValue(); - } - -// Contact operations - public override async Task AddContact(AddContactRequest request, - ServerCallContext context) - { - if (!Guid.TryParse(request.AccountId, out var accountId)) - throw new RpcException(new Grpc.Core.Status(StatusCode.InvalidArgument, "Invalid account ID format")); - - var contact = new AccountContact - { - AccountId = accountId, - Type = (AccountContactType)request.Type, - Content = request.Content, - IsPrimary = request.IsPrimary, - VerifiedAt = null - }; - - _db.AccountContacts.Add(contact); - await _db.SaveChangesAsync(); - - return contact.ToProtoValue(); - } - - public override async Task RemoveContact(RemoveContactRequest request, ServerCallContext context) - { - if (!Guid.TryParse(request.AccountId, out var accountId)) - throw new RpcException(new Grpc.Core.Status(StatusCode.InvalidArgument, "Invalid account ID format")); - - if (!Guid.TryParse(request.Id, out var contactId)) - throw new RpcException(new Grpc.Core.Status(StatusCode.InvalidArgument, "Invalid contact ID format")); - - var contact = await _db.AccountContacts.FirstOrDefaultAsync(c => c.Id == contactId && c.AccountId == accountId); - if (contact == null) - throw new RpcException(new Grpc.Core.Status(StatusCode.NotFound, "Contact not found.")); - - _db.AccountContacts.Remove(contact); - await _db.SaveChangesAsync(); - - return new Empty(); - } - - public override async Task ListContacts(ListContactsRequest request, - ServerCallContext context) - { - if (!Guid.TryParse(request.AccountId, out var accountId)) - throw new RpcException(new Grpc.Core.Status(StatusCode.InvalidArgument, "Invalid account ID format")); - - var query = _db.AccountContacts.AsNoTracking().Where(c => c.AccountId == accountId); - - if (request.VerifiedOnly) - query = query.Where(c => c.VerifiedAt != null); - - var contacts = await query.ToListAsync(); - - var response = new ListContactsResponse(); - response.Contacts.AddRange(contacts.Select(c => c.ToProtoValue())); - - return response; - } - - public override async Task VerifyContact(VerifyContactRequest request, - ServerCallContext context) - { - // This is a placeholder implementation. In a real-world scenario, you would - // have a more robust verification mechanism (e.g., sending a code to the - // user's email or phone). - if (!Guid.TryParse(request.AccountId, out var accountId)) - throw new RpcException(new Grpc.Core.Status(StatusCode.InvalidArgument, "Invalid account ID format")); - - if (!Guid.TryParse(request.Id, out var contactId)) - throw new RpcException(new Grpc.Core.Status(StatusCode.InvalidArgument, "Invalid contact ID format")); - - var contact = await _db.AccountContacts.FirstOrDefaultAsync(c => c.Id == contactId && c.AccountId == accountId); - if (contact == null) - throw new RpcException(new Grpc.Core.Status(StatusCode.NotFound, "Contact not found.")); - - contact.VerifiedAt = _clock.GetCurrentInstant(); - await _db.SaveChangesAsync(); - - return contact.ToProtoValue(); - } - -// Badge operations - public override async Task AddBadge(AddBadgeRequest request, ServerCallContext context) - { - if (!Guid.TryParse(request.AccountId, out var accountId)) - throw new RpcException(new Grpc.Core.Status(StatusCode.InvalidArgument, "Invalid account ID format")); - - var badge = new AccountBadge - { - AccountId = accountId, - Type = request.Type, - Label = request.Label, - Caption = request.Caption, - ActivatedAt = _clock.GetCurrentInstant(), - ExpiredAt = request.ExpiredAt?.ToInstant(), - Meta = request.Meta.ToDictionary(kvp => kvp.Key, kvp => (object)kvp.Value) - }; - - _db.Badges.Add(badge); - await _db.SaveChangesAsync(); - - return badge.ToProtoValue(); - } - - public override async Task RemoveBadge(RemoveBadgeRequest request, ServerCallContext context) - { - if (!Guid.TryParse(request.AccountId, out var accountId)) - throw new RpcException(new Grpc.Core.Status(StatusCode.InvalidArgument, "Invalid account ID format")); - - if (!Guid.TryParse(request.Id, out var badgeId)) - throw new RpcException(new Grpc.Core.Status(StatusCode.InvalidArgument, "Invalid badge ID format")); - - var badge = await _db.Badges.FirstOrDefaultAsync(b => b.Id == badgeId && b.AccountId == accountId); - if (badge == null) - throw new RpcException(new Grpc.Core.Status(StatusCode.NotFound, "Badge not found.")); - - _db.Badges.Remove(badge); - await _db.SaveChangesAsync(); - - return new Empty(); - } - - public override async Task ListBadges(ListBadgesRequest request, ServerCallContext context) - { - if (!Guid.TryParse(request.AccountId, out var accountId)) - throw new RpcException(new Grpc.Core.Status(StatusCode.InvalidArgument, "Invalid account ID format")); - - var query = _db.Badges.AsNoTracking().Where(b => b.AccountId == accountId); - - if (request.ActiveOnly) - query = query.Where(b => b.ExpiredAt == null || b.ExpiredAt > _clock.GetCurrentInstant()); - - var badges = await query.ToListAsync(); - - var response = new ListBadgesResponse(); - response.Badges.AddRange(badges.Select(b => b.ToProtoValue())); - - return response; - } - - public override async Task SetActiveBadge(SetActiveBadgeRequest request, - ServerCallContext context) - { - if (!Guid.TryParse(request.AccountId, out var accountId)) - throw new RpcException(new Grpc.Core.Status(StatusCode.InvalidArgument, "Invalid account ID format")); - - var profile = await _db.AccountProfiles.FirstOrDefaultAsync(p => p.AccountId == accountId); - if (profile == null) - throw new RpcException(new Grpc.Core.Status(StatusCode.NotFound, "Profile not found.")); - - if (!string.IsNullOrEmpty(request.BadgeId) && !Guid.TryParse(request.BadgeId, out var badgeId)) - throw new RpcException(new Grpc.Core.Status(StatusCode.InvalidArgument, "Invalid badge ID format")); - - await _db.SaveChangesAsync(); - - return profile.ToProtoValue(); - } - public override async Task ListFriends( ListUserRelationshipSimpleRequest request, ServerCallContext context) { diff --git a/DysonNetwork.Pass/Account/ActionLog.cs b/DysonNetwork.Pass/Account/ActionLog.cs index 32f0c3f..904064f 100644 --- a/DysonNetwork.Pass/Account/ActionLog.cs +++ b/DysonNetwork.Pass/Account/ActionLog.cs @@ -1,6 +1,8 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using DysonNetwork.Shared.Data; +using DysonNetwork.Shared.Proto; +using NodaTime.Serialization.Protobuf; using Point = NetTopologySuite.Geometries.Point; namespace DysonNetwork.Pass.Account; @@ -56,4 +58,26 @@ public class ActionLog : ModelBase public Guid AccountId { get; set; } public Account Account { get; set; } = null!; public Guid? SessionId { get; set; } + + public Shared.Proto.ActionLog ToProtoValue() + { + var protoLog = new Shared.Proto.ActionLog + { + Id = Id.ToString(), + Action = Action, + UserAgent = UserAgent ?? string.Empty, + IpAddress = IpAddress ?? string.Empty, + Location = Location?.ToString() ?? string.Empty, + AccountId = AccountId.ToString(), + CreatedAt = CreatedAt.ToTimestamp() + }; + + // Convert Meta dictionary to Struct + protoLog.Meta.Add(GrpcTypeHelper.ConvertToValueMap(Meta)); + + if (SessionId.HasValue) + protoLog.SessionId = SessionId.Value.ToString(); + + return protoLog; + } } \ No newline at end of file diff --git a/DysonNetwork.Pass/Account/ActionLogService.cs b/DysonNetwork.Pass/Account/ActionLogService.cs index 2dff230..3860748 100644 --- a/DysonNetwork.Pass/Account/ActionLogService.cs +++ b/DysonNetwork.Pass/Account/ActionLogService.cs @@ -5,7 +5,7 @@ namespace DysonNetwork.Pass.Account; public class ActionLogService(GeoIpService geo, FlushBufferService fbs) { - public void CreateActionLog(Guid accountId, string action, Dictionary meta) + public void CreateActionLog(Guid accountId, string action, Dictionary meta) { var log = new ActionLog { diff --git a/DysonNetwork.Pass/Account/ActionLogServiceGrpc.cs b/DysonNetwork.Pass/Account/ActionLogServiceGrpc.cs new file mode 100644 index 0000000..e81f2b6 --- /dev/null +++ b/DysonNetwork.Pass/Account/ActionLogServiceGrpc.cs @@ -0,0 +1,114 @@ +using DysonNetwork.Shared.Proto; +using Grpc.Core; +using Microsoft.EntityFrameworkCore; +using NodaTime; + +namespace DysonNetwork.Pass.Account; + +public class ActionLogServiceGrpc : Shared.Proto.ActionLogService.ActionLogServiceBase +{ + private readonly ActionLogService _actionLogService; + private readonly AppDatabase _db; + private readonly ILogger _logger; + + public ActionLogServiceGrpc( + ActionLogService actionLogService, + AppDatabase db, + ILogger logger) + { + _actionLogService = actionLogService ?? throw new ArgumentNullException(nameof(actionLogService)); + _db = db ?? throw new ArgumentNullException(nameof(db)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + public override async Task CreateActionLog(CreateActionLogRequest request, + ServerCallContext context) + { + if (string.IsNullOrEmpty(request.AccountId) || !Guid.TryParse(request.AccountId, out var accountId)) + { + throw new RpcException(new Grpc.Core.Status(Grpc.Core.StatusCode.InvalidArgument, "Invalid account ID")); + } + + try + { + var meta = request.Meta + ?.Select(x => new KeyValuePair(x.Key, GrpcTypeHelper.ConvertField(x.Value))) + .ToDictionary() ?? new Dictionary(); + + _actionLogService.CreateActionLog( + accountId, + request.Action, + meta + ); + + return new CreateActionLogResponse(); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error creating action log"); + throw new RpcException(new Grpc.Core.Status(Grpc.Core.StatusCode.Internal, "Failed to create action log")); + } + } + + public override async Task ListActionLogs(ListActionLogsRequest request, + ServerCallContext context) + { + if (string.IsNullOrEmpty(request.AccountId) || !Guid.TryParse(request.AccountId, out var accountId)) + { + throw new RpcException(new Grpc.Core.Status(Grpc.Core.StatusCode.InvalidArgument, "Invalid account ID")); + } + + try + { + var query = _db.ActionLogs + .AsNoTracking() + .Where(log => log.AccountId == accountId); + + if (!string.IsNullOrEmpty(request.Action)) + { + query = query.Where(log => log.Action == request.Action); + } + + // Apply ordering (default to newest first) + query = (request.OrderBy?.ToLower() ?? "createdat desc") switch + { + "createdat" => query.OrderBy(log => log.CreatedAt), + "createdat desc" => query.OrderByDescending(log => log.CreatedAt), + _ => query.OrderByDescending(log => log.CreatedAt) + }; + + // Apply pagination + var pageSize = request.PageSize == 0 ? 50 : Math.Min(request.PageSize, 1000); + var logs = await query + .Take(pageSize + 1) // Fetch one extra to determine if there are more pages + .ToListAsync(); + + var hasMore = logs.Count > pageSize; + if (hasMore) + { + logs.RemoveAt(logs.Count - 1); + } + + var response = new ListActionLogsResponse + { + TotalSize = await query.CountAsync() + }; + + if (hasMore) + { + // In a real implementation, you'd generate a proper page token + response.NextPageToken = (logs.LastOrDefault()?.CreatedAt ?? SystemClock.Instance.GetCurrentInstant()) + .ToString(); + } + + response.ActionLogs.AddRange(logs.Select(log => log.ToProtoValue())); + + return response; + } + catch (Exception ex) + { + _logger.LogError(ex, "Error listing action logs"); + throw new RpcException(new Grpc.Core.Status(Grpc.Core.StatusCode.Internal, "Failed to list action logs")); + } + } +} \ No newline at end of file diff --git a/DysonNetwork.Pass/Startup/ApplicationConfiguration.cs b/DysonNetwork.Pass/Startup/ApplicationConfiguration.cs index 8ee7fa8..df51b15 100644 --- a/DysonNetwork.Pass/Startup/ApplicationConfiguration.cs +++ b/DysonNetwork.Pass/Startup/ApplicationConfiguration.cs @@ -70,6 +70,7 @@ public static class ApplicationConfiguration { app.MapGrpcService(); app.MapGrpcService(); + app.MapGrpcService(); return app; } diff --git a/DysonNetwork.Shared/Proto/account.proto b/DysonNetwork.Shared/Proto/account.proto index c66e304..7aad9c7 100644 --- a/DysonNetwork.Shared/Proto/account.proto +++ b/DysonNetwork.Shared/Proto/account.proto @@ -174,6 +174,19 @@ message LevelingInfo { repeated int32 experience_per_level = 6; } +// ActionLog represents a record of an action taken by a user +message ActionLog { + string id = 1; // Unique identifier for the log entry + string action = 2; // The action that was performed, e.g., "user.login" + map meta = 3; // Metadata associated with the action + google.protobuf.StringValue user_agent = 4; // User agent of the client + google.protobuf.StringValue ip_address = 5; // IP address of the client + google.protobuf.StringValue location = 6; // Geographic location of the client, derived from IP + string account_id = 7; // The account that performed the action + google.protobuf.StringValue session_id = 8; // The session in which the action was performed + google.protobuf.Timestamp created_at = 9; // When the action log was created +} + // ==================================== // Service Definitions // ==================================== @@ -209,10 +222,45 @@ service AccountService { rpc ListBlocked(ListUserRelationshipSimpleRequest) returns (ListUserRelationshipSimpleResponse) {} } +// ActionLogService provides operations for action logs +service ActionLogService { + rpc CreateActionLog(CreateActionLogRequest) returns (CreateActionLogResponse) {} + rpc ListActionLogs(ListActionLogsRequest) returns (ListActionLogsResponse) {} +} + // ==================================== // Request/Response Messages // ==================================== +// ActionLog Requests/Responses +message CreateActionLogRequest { + string action = 1; + map meta = 2; + google.protobuf.StringValue user_agent = 3; + google.protobuf.StringValue ip_address = 4; + google.protobuf.StringValue location = 5; + string account_id = 6; + google.protobuf.StringValue session_id = 7; +} + +message CreateActionLogResponse { + ActionLog action_log = 1; +} + +message ListActionLogsRequest { + string account_id = 1; + string action = 2; + int32 page_size = 3; + string page_token = 4; + string order_by = 5; +} + +message ListActionLogsResponse { + repeated ActionLog action_logs = 1; + string next_page_token = 2; + int32 total_size = 3; +} + // Account Requests/Responses message GetAccountRequest { string id = 1; // Account ID to retrieve diff --git a/DysonNetwork.Sphere/Post/PostController.cs b/DysonNetwork.Sphere/Post/PostController.cs index 7e9dda0..3def48a 100644 --- a/DysonNetwork.Sphere/Post/PostController.cs +++ b/DysonNetwork.Sphere/Post/PostController.cs @@ -1,15 +1,11 @@ using System.ComponentModel.DataAnnotations; -using System.Text.Json; -using DysonNetwork.Sphere.Account; -using DysonNetwork.Sphere.Pages.Posts; +using DysonNetwork.Shared.Proto; using DysonNetwork.Sphere.Permission; using DysonNetwork.Sphere.Publisher; -using DysonNetwork.Sphere.Storage; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using NodaTime; -using NpgsqlTypes; namespace DysonNetwork.Sphere.Post; @@ -19,8 +15,8 @@ public class PostController( AppDatabase db, PostService ps, PublisherService pub, - RelationshipService rels, - ActionLogService als + AccountService.AccountServiceClient accounts, + ActionLogService.ActionLogServiceClient als ) : ControllerBase { From 3c11c4f3be353d4cdd8022defb7cfaaec217d5b8 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Tue, 15 Jul 2025 01:54:27 +0800 Subject: [PATCH 19/42] :recycle: I have no idea what I have done --- .../Account/AccountServiceGrpc.cs | 12 +- DysonNetwork.Pass/Account/ActionLog.cs | 39 - DysonNetwork.Pass/Account/Relationship.cs | 2 +- DysonNetwork.Pass/Auth/AuthController.cs | 1 + .../Connection/WebSocketPacket.cs | 9 - .../Connection/WebSocketService.cs | 1 + DysonNetwork.Shared/Data/ActionLog.cs | 40 + DysonNetwork.Shared/Data/WebSocket.cs | 11 + DysonNetwork.Shared/Proto/GrpcTypeHelper.cs | 15 + DysonNetwork.Shared/Proto/account.proto | 10 +- .../Activity/ActivityService.cs | 13 +- DysonNetwork.Sphere/AppDatabase.cs | 53 - DysonNetwork.Sphere/Chat/ChatController.cs | 2 +- .../Chat/ChatRoomController.cs | 393 +++++--- .../Chat/ChatRoomController.cs.bak | 947 ++++++++++++++++++ DysonNetwork.Sphere/Chat/ChatService.cs | 91 +- .../Chat/Realtime/IRealtimeService.cs | 1 + .../Chat/Realtime/LivekitService.cs | 100 +- .../Chat/RealtimeCallController.cs | 15 +- DysonNetwork.Sphere/Developer/CustomApp.cs | 3 +- .../Developer/CustomAppController.cs | 8 +- .../Developer/CustomAppService.cs | 106 +- .../Developer/DeveloperController.cs | 25 +- .../Pages/Posts/PostDetail.cshtml.cs | 2 +- .../Pages/Shared/_Layout.cshtml | 26 - DysonNetwork.Sphere/Permission/Permission.cs | 59 -- .../Permission/PermissionMiddleware.cs | 51 - .../Permission/PermissionService.cs | 197 ---- DysonNetwork.Sphere/Post/PostController.cs | 131 ++- DysonNetwork.Sphere/Post/PostService.cs | 1 - DysonNetwork.Sphere/Publisher/Publisher.cs | 3 + .../Publisher/PublisherController.cs | 311 ++++-- .../PublisherSubscriptionController.cs | 9 +- DysonNetwork.Sphere/Realm/RealmController.cs | 2 - .../Startup/ServiceCollectionExtensions.cs | 2 +- 35 files changed, 1761 insertions(+), 930 deletions(-) create mode 100644 DysonNetwork.Shared/Data/ActionLog.cs create mode 100644 DysonNetwork.Shared/Data/WebSocket.cs create mode 100644 DysonNetwork.Sphere/Chat/ChatRoomController.cs.bak delete mode 100644 DysonNetwork.Sphere/Permission/Permission.cs delete mode 100644 DysonNetwork.Sphere/Permission/PermissionMiddleware.cs delete mode 100644 DysonNetwork.Sphere/Permission/PermissionService.cs diff --git a/DysonNetwork.Pass/Account/AccountServiceGrpc.cs b/DysonNetwork.Pass/Account/AccountServiceGrpc.cs index 1642ed4..9402ef5 100644 --- a/DysonNetwork.Pass/Account/AccountServiceGrpc.cs +++ b/DysonNetwork.Pass/Account/AccountServiceGrpc.cs @@ -99,22 +99,22 @@ public class AccountServiceGrpc( return response; } - public override async Task ListFriends( - ListUserRelationshipSimpleRequest request, ServerCallContext context) + public override async Task ListFriends( + ListRelationshipSimpleRequest request, ServerCallContext context) { var accountId = Guid.Parse(request.AccountId); var relationship = await relationships.ListAccountFriends(accountId); - var resp = new ListUserRelationshipSimpleResponse(); + var resp = new ListRelationshipSimpleResponse(); resp.AccountsId.AddRange(relationship.Select(x => x.ToString())); return resp; } - public override async Task ListBlocked( - ListUserRelationshipSimpleRequest request, ServerCallContext context) + public override async Task ListBlocked( + ListRelationshipSimpleRequest request, ServerCallContext context) { var accountId = Guid.Parse(request.AccountId); var relationship = await relationships.ListAccountBlocked(accountId); - var resp = new ListUserRelationshipSimpleResponse(); + var resp = new ListRelationshipSimpleResponse(); resp.AccountsId.AddRange(relationship.Select(x => x.ToString())); return resp; } diff --git a/DysonNetwork.Pass/Account/ActionLog.cs b/DysonNetwork.Pass/Account/ActionLog.cs index 904064f..827ff20 100644 --- a/DysonNetwork.Pass/Account/ActionLog.cs +++ b/DysonNetwork.Pass/Account/ActionLog.cs @@ -7,45 +7,6 @@ using Point = NetTopologySuite.Geometries.Point; namespace DysonNetwork.Pass.Account; -public abstract class ActionLogType -{ - public const string NewLogin = "login"; - public const string ChallengeAttempt = "challenges.attempt"; - public const string ChallengeSuccess = "challenges.success"; - public const string ChallengeFailure = "challenges.failure"; - public const string PostCreate = "posts.create"; - public const string PostUpdate = "posts.update"; - public const string PostDelete = "posts.delete"; - public const string PostReact = "posts.react"; - public const string MessageCreate = "messages.create"; - public const string MessageUpdate = "messages.update"; - public const string MessageDelete = "messages.delete"; - public const string MessageReact = "messages.react"; - public const string PublisherCreate = "publishers.create"; - public const string PublisherUpdate = "publishers.update"; - public const string PublisherDelete = "publishers.delete"; - public const string PublisherMemberInvite = "publishers.members.invite"; - public const string PublisherMemberJoin = "publishers.members.join"; - public const string PublisherMemberLeave = "publishers.members.leave"; - public const string PublisherMemberKick = "publishers.members.kick"; - public const string RealmCreate = "realms.create"; - public const string RealmUpdate = "realms.update"; - public const string RealmDelete = "realms.delete"; - public const string RealmInvite = "realms.invite"; - public const string RealmJoin = "realms.join"; - public const string RealmLeave = "realms.leave"; - public const string RealmKick = "realms.kick"; - public const string RealmAdjustRole = "realms.role.edit"; - public const string ChatroomCreate = "chatrooms.create"; - public const string ChatroomUpdate = "chatrooms.update"; - public const string ChatroomDelete = "chatrooms.delete"; - public const string ChatroomInvite = "chatrooms.invite"; - public const string ChatroomJoin = "chatrooms.join"; - public const string ChatroomLeave = "chatrooms.leave"; - public const string ChatroomKick = "chatrooms.kick"; - public const string ChatroomAdjustRole = "chatrooms.role.edit"; -} - public class ActionLog : ModelBase { public Guid Id { get; set; } = Guid.NewGuid(); diff --git a/DysonNetwork.Pass/Account/Relationship.cs b/DysonNetwork.Pass/Account/Relationship.cs index 0b169b7..9c605ee 100644 --- a/DysonNetwork.Pass/Account/Relationship.cs +++ b/DysonNetwork.Pass/Account/Relationship.cs @@ -29,7 +29,7 @@ public class Relationship : ModelBase RelatedId = RelatedId.ToString(), Account = Account.ToProtoValue(), Related = Related.ToProtoValue(), - Type = (int)Status, + Status = (int)Status, CreatedAt = CreatedAt.ToTimestamp(), UpdatedAt = UpdatedAt.ToTimestamp() }; diff --git a/DysonNetwork.Pass/Auth/AuthController.cs b/DysonNetwork.Pass/Auth/AuthController.cs index 9722004..5232165 100644 --- a/DysonNetwork.Pass/Auth/AuthController.cs +++ b/DysonNetwork.Pass/Auth/AuthController.cs @@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Mvc; using NodaTime; using Microsoft.EntityFrameworkCore; using DysonNetwork.Pass.Account; +using DysonNetwork.Shared.Data; using DysonNetwork.Shared.GeoIp; namespace DysonNetwork.Pass.Auth; diff --git a/DysonNetwork.Pusher/Connection/WebSocketPacket.cs b/DysonNetwork.Pusher/Connection/WebSocketPacket.cs index f20b961..cbb2f50 100644 --- a/DysonNetwork.Pusher/Connection/WebSocketPacket.cs +++ b/DysonNetwork.Pusher/Connection/WebSocketPacket.cs @@ -4,15 +4,6 @@ using NodaTime.Serialization.SystemTextJson; namespace DysonNetwork.Pusher.Connection; -public abstract class WebSocketPacketType -{ - public const string Error = "error"; - public const string MessageNew = "messages.new"; - public const string MessageUpdate = "messages.update"; - public const string MessageDelete = "messages.delete"; - public const string CallParticipantsUpdate = "call.participants.update"; -} - public class WebSocketPacket { public string Type { get; set; } = null!; diff --git a/DysonNetwork.Pusher/Connection/WebSocketService.cs b/DysonNetwork.Pusher/Connection/WebSocketService.cs index c9d9af9..94eeaaf 100644 --- a/DysonNetwork.Pusher/Connection/WebSocketService.cs +++ b/DysonNetwork.Pusher/Connection/WebSocketService.cs @@ -1,5 +1,6 @@ using System.Collections.Concurrent; using System.Net.WebSockets; +using DysonNetwork.Shared.Data; using DysonNetwork.Shared.Proto; namespace DysonNetwork.Pusher.Connection; diff --git a/DysonNetwork.Shared/Data/ActionLog.cs b/DysonNetwork.Shared/Data/ActionLog.cs new file mode 100644 index 0000000..dd2e805 --- /dev/null +++ b/DysonNetwork.Shared/Data/ActionLog.cs @@ -0,0 +1,40 @@ +namespace DysonNetwork.Shared.Data; + +public abstract class ActionLogType +{ + public const string NewLogin = "login"; + public const string ChallengeAttempt = "challenges.attempt"; + public const string ChallengeSuccess = "challenges.success"; + public const string ChallengeFailure = "challenges.failure"; + public const string PostCreate = "posts.create"; + public const string PostUpdate = "posts.update"; + public const string PostDelete = "posts.delete"; + public const string PostReact = "posts.react"; + public const string MessageCreate = "messages.create"; + public const string MessageUpdate = "messages.update"; + public const string MessageDelete = "messages.delete"; + public const string MessageReact = "messages.react"; + public const string PublisherCreate = "publishers.create"; + public const string PublisherUpdate = "publishers.update"; + public const string PublisherDelete = "publishers.delete"; + public const string PublisherMemberInvite = "publishers.members.invite"; + public const string PublisherMemberJoin = "publishers.members.join"; + public const string PublisherMemberLeave = "publishers.members.leave"; + public const string PublisherMemberKick = "publishers.members.kick"; + public const string RealmCreate = "realms.create"; + public const string RealmUpdate = "realms.update"; + public const string RealmDelete = "realms.delete"; + public const string RealmInvite = "realms.invite"; + public const string RealmJoin = "realms.join"; + public const string RealmLeave = "realms.leave"; + public const string RealmKick = "realms.kick"; + public const string RealmAdjustRole = "realms.role.edit"; + public const string ChatroomCreate = "chatrooms.create"; + public const string ChatroomUpdate = "chatrooms.update"; + public const string ChatroomDelete = "chatrooms.delete"; + public const string ChatroomInvite = "chatrooms.invite"; + public const string ChatroomJoin = "chatrooms.join"; + public const string ChatroomLeave = "chatrooms.leave"; + public const string ChatroomKick = "chatrooms.kick"; + public const string ChatroomAdjustRole = "chatrooms.role.edit"; +} \ No newline at end of file diff --git a/DysonNetwork.Shared/Data/WebSocket.cs b/DysonNetwork.Shared/Data/WebSocket.cs new file mode 100644 index 0000000..b7f8be1 --- /dev/null +++ b/DysonNetwork.Shared/Data/WebSocket.cs @@ -0,0 +1,11 @@ +namespace DysonNetwork.Shared.Data; + +public abstract class WebSocketPacketType +{ + public const string Error = "error"; + public const string MessageNew = "messages.new"; + public const string MessageUpdate = "messages.update"; + public const string MessageDelete = "messages.delete"; + public const string CallParticipantsUpdate = "call.participants.update"; +} + diff --git a/DysonNetwork.Shared/Proto/GrpcTypeHelper.cs b/DysonNetwork.Shared/Proto/GrpcTypeHelper.cs index d81d092..7ed044c 100644 --- a/DysonNetwork.Shared/Proto/GrpcTypeHelper.cs +++ b/DysonNetwork.Shared/Proto/GrpcTypeHelper.cs @@ -86,4 +86,19 @@ public abstract class GrpcTypeHelper _ => JsonConvert.DeserializeObject(JsonConvert.SerializeObject(value, SerializerSettings)) }; } + + public static Value ConvertObjectToValue(object? obj) + { + return obj switch + { + string s => Value.ForString(s), + int i => Value.ForNumber(i), + long l => Value.ForNumber(l), + float f => Value.ForNumber(f), + double d => Value.ForNumber(d), + bool b => Value.ForBool(b), + null => Value.ForNull(), + _ => Value.ForString(JsonConvert.SerializeObject(obj, SerializerSettings)) // fallback to JSON string + }; + } } \ No newline at end of file diff --git a/DysonNetwork.Shared/Proto/account.proto b/DysonNetwork.Shared/Proto/account.proto index 7aad9c7..1a2a0cb 100644 --- a/DysonNetwork.Shared/Proto/account.proto +++ b/DysonNetwork.Shared/Proto/account.proto @@ -159,7 +159,7 @@ message Relationship { string related_id = 2; optional Account account = 3; optional Account related = 4; - int32 type = 5; + int32 status = 5; google.protobuf.Timestamp created_at = 6; google.protobuf.Timestamp updated_at = 7; } @@ -218,8 +218,8 @@ service AccountService { rpc GetRelationship(GetRelationshipRequest) returns (GetRelationshipResponse) {} rpc HasRelationship(GetRelationshipRequest) returns (google.protobuf.BoolValue) {} - rpc ListFriends(ListUserRelationshipSimpleRequest) returns (ListUserRelationshipSimpleResponse) {} - rpc ListBlocked(ListUserRelationshipSimpleRequest) returns (ListUserRelationshipSimpleResponse) {} + rpc ListFriends(ListRelationshipSimpleRequest) returns (ListRelationshipSimpleResponse) {} + rpc ListBlocked(ListRelationshipSimpleRequest) returns (ListRelationshipSimpleResponse) {} } // ActionLogService provides operations for action logs @@ -396,10 +396,10 @@ message GetRelationshipResponse { optional Relationship relationship = 1; } -message ListUserRelationshipSimpleRequest { +message ListRelationshipSimpleRequest { string account_id = 1; } -message ListUserRelationshipSimpleResponse { +message ListRelationshipSimpleResponse { repeated string accounts_id = 1; } \ No newline at end of file diff --git a/DysonNetwork.Sphere/Activity/ActivityService.cs b/DysonNetwork.Sphere/Activity/ActivityService.cs index 823c84b..b1cf614 100644 --- a/DysonNetwork.Sphere/Activity/ActivityService.cs +++ b/DysonNetwork.Sphere/Activity/ActivityService.cs @@ -1,5 +1,4 @@ using DysonNetwork.Shared.Proto; -using DysonNetwork.Sphere.Account; using DysonNetwork.Sphere.WebReader; using DysonNetwork.Sphere.Discovery; using DysonNetwork.Sphere.Post; @@ -12,9 +11,10 @@ namespace DysonNetwork.Sphere.Activity; public class ActivityService( AppDatabase db, PublisherService pub, - RelationshipService rels, PostService ps, - DiscoveryService ds) + DiscoveryService ds, + AccountService.AccountServiceClient accounts +) { private static double CalculateHotRank(Post.Post post, Instant now) { @@ -125,7 +125,9 @@ public class ActivityService( ) { var activities = new List(); - var userFriends = await rels.ListAccountFriends(currentUser); + var friendsResponse = await accounts.ListFriendsAsync(new ListRelationshipSimpleRequest + { AccountId = currentUser.Id }); + var userFriends = friendsResponse.AccountsId.Select(Guid.Parse).ToList(); var userPublishers = await pub.GetUserPublishers(Guid.Parse(currentUser.Id)); debugInclude ??= []; @@ -242,8 +244,7 @@ public class ActivityService( .ToList(); // Formatting data - foreach (var post in rankedPosts) - activities.Add(post.ToActivity()); + activities.AddRange(rankedPosts.Select(post => post.ToActivity())); if (activities.Count == 0) activities.Add(Activity.Empty()); diff --git a/DysonNetwork.Sphere/AppDatabase.cs b/DysonNetwork.Sphere/AppDatabase.cs index e77bcbc..9b851d5 100644 --- a/DysonNetwork.Sphere/AppDatabase.cs +++ b/DysonNetwork.Sphere/AppDatabase.cs @@ -2,7 +2,6 @@ using System.Linq.Expressions; using System.Reflection; using DysonNetwork.Sphere.Chat; using DysonNetwork.Sphere.Developer; -using DysonNetwork.Sphere.Permission; using DysonNetwork.Sphere.Post; using DysonNetwork.Sphere.Publisher; using DysonNetwork.Sphere.Realm; @@ -32,10 +31,6 @@ public class AppDatabase( IConfiguration configuration ) : DbContext(options) { - public DbSet PermissionNodes { get; set; } - public DbSet PermissionGroups { get; set; } - public DbSet PermissionGroupMembers { get; set; } - public DbSet Publishers { get; set; } public DbSet PublisherMembers { get; set; } public DbSet PublisherSubscriptions { get; set; } @@ -78,38 +73,6 @@ public class AppDatabase( .UseNodaTime() ).UseSnakeCaseNamingConvention(); - optionsBuilder.UseAsyncSeeding(async (context, _, cancellationToken) => - { - var defaultPermissionGroup = await context.Set() - .FirstOrDefaultAsync(g => g.Key == "default", cancellationToken); - if (defaultPermissionGroup is null) - { - context.Set().Add(new PermissionGroup - { - Key = "default", - Nodes = new List - { - "posts.create", - "posts.react", - "publishers.create", - "files.create", - "chat.create", - "chat.messages.create", - "chat.realtime.create", - "accounts.statuses.create", - "accounts.statuses.update", - "stickers.packs.create", - "stickers.create" - }.Select(permission => - PermissionService.NewPermissionNode("group:default", "global", permission, true)) - .ToList() - }); - await context.SaveChangesAsync(cancellationToken); - } - }); - - optionsBuilder.UseSeeding((context, _) => {}); - base.OnConfiguring(optionsBuilder); } @@ -117,14 +80,6 @@ public class AppDatabase( { base.OnModelCreating(modelBuilder); - modelBuilder.Entity() - .HasKey(pg => new { pg.GroupId, pg.Actor }); - modelBuilder.Entity() - .HasOne(pg => pg.Group) - .WithMany(g => g.Members) - .HasForeignKey(pg => pg.GroupId) - .OnDelete(DeleteBehavior.Cascade); - modelBuilder.Entity() .HasKey(pm => new { pm.PublisherId, pm.AccountId }); modelBuilder.Entity() @@ -289,14 +244,6 @@ public class AppDatabaseRecyclingJob(AppDatabase db, ILogger x.ExpiredAt != null && x.ExpiredAt <= now) - .ExecuteDeleteAsync(); - logger.LogDebug("Removed {Count} records of expired permission group members.", affectedRows); - logger.LogInformation("Deleting soft-deleted records..."); var threshold = now - Duration.FromDays(7); diff --git a/DysonNetwork.Sphere/Chat/ChatController.cs b/DysonNetwork.Sphere/Chat/ChatController.cs index dc1a5db..ee54c05 100644 --- a/DysonNetwork.Sphere/Chat/ChatController.cs +++ b/DysonNetwork.Sphere/Chat/ChatController.cs @@ -1,9 +1,9 @@ using System.ComponentModel.DataAnnotations; using System.Text.RegularExpressions; +using DysonNetwork.Shared.Auth; using DysonNetwork.Shared.Content; using DysonNetwork.Shared.Data; using DysonNetwork.Shared.Proto; -using DysonNetwork.Sphere.Permission; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; diff --git a/DysonNetwork.Sphere/Chat/ChatRoomController.cs b/DysonNetwork.Sphere/Chat/ChatRoomController.cs index f9cb452..c5304d9 100644 --- a/DysonNetwork.Sphere/Chat/ChatRoomController.cs +++ b/DysonNetwork.Sphere/Chat/ChatRoomController.cs @@ -1,10 +1,13 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using System.ComponentModel.DataAnnotations; +using DysonNetwork.Shared; +using DysonNetwork.Shared.Data; using DysonNetwork.Shared.Proto; using DysonNetwork.Sphere.Localization; using DysonNetwork.Sphere.Permission; using DysonNetwork.Sphere.Realm; +using Grpc.Core; using Microsoft.AspNetCore.Authorization; using Microsoft.Extensions.Localization; using NodaTime; @@ -20,7 +23,9 @@ public class ChatRoomController( IStringLocalizer localizer, AccountService.AccountServiceClient accounts, FileService.FileServiceClient files, - FileReferenceService.FileReferenceServiceClient fileRefs + FileReferenceService.FileReferenceServiceClient fileRefs, + ActionLogService.ActionLogServiceClient als, + PusherService.PusherServiceClient pusher ) : ControllerBase { [HttpGet("{id:guid}")] @@ -123,10 +128,14 @@ public class ChatRoomController( db.ChatRooms.Add(dmRoom); await db.SaveChangesAsync(); - als.CreateActionLogFromRequest( - ActionLogType.ChatroomCreate, - new Dictionary { { "chatroom_id", dmRoom.Id } }, Request - ); + _ = als.CreateActionLogAsync(new CreateActionLogRequest + { + Action = "chatrooms.create", + Meta = { { "chatroom_id", Google.Protobuf.WellKnownTypes.Value.ForString(dmRoom.Id.ToString()) } }, + AccountId = currentUser.Id, + UserAgent = Request.Headers.UserAgent, + IpAddress = Request.HttpContext.Connection.RemoteIpAddress?.ToString() + }); var invitedMember = dmRoom.Members.First(m => m.AccountId == request.RelatedUserId); invitedMember.ChatRoom = dmRoom; @@ -200,23 +209,44 @@ public class ChatRoomController( if (request.PictureId is not null) { - chatRoom.Picture = (await db.Files.FindAsync(request.PictureId))?.ToReferenceObject(); - if (chatRoom.Picture is null) return BadRequest("Invalid picture id, unable to find the file on cloud."); - await fileRefs.CreateReferenceAsync( - new CreateReferenceRequest + try + { + var fileResponse = await files.GetFileAsync(new GetFileRequest { Id = request.PictureId }); + if (fileResponse == null) return BadRequest("Invalid picture id, unable to find the file on cloud."); + chatRoom.Picture = CloudFileReferenceObject.FromProtoValue(fileResponse); + + await fileRefs.CreateReferenceAsync(new CreateReferenceRequest { - FileId = publisher.Picture.Id, - Usage = "publisher.picture", - ResourceId = publisher.ResourceIdentifier, - } - ); + FileId = fileResponse.Id, + Usage = "chatroom.picture", + ResourceId = chatRoom.ResourceIdentifier, + }); + } + catch (RpcException ex) when (ex.StatusCode == Grpc.Core.StatusCode.NotFound) + { + return BadRequest("Invalid picture id, unable to find the file on cloud."); + } } if (request.BackgroundId is not null) { - chatRoom.Background = (await db.Files.FindAsync(request.BackgroundId))?.ToReferenceObject(); - if (chatRoom.Background is null) + try + { + var fileResponse = await files.GetFileAsync(new GetFileRequest { Id = request.BackgroundId }); + if (fileResponse == null) return BadRequest("Invalid background id, unable to find the file on cloud."); + chatRoom.Background = CloudFileReferenceObject.FromProtoValue(fileResponse); + + await fileRefs.CreateReferenceAsync(new CreateReferenceRequest + { + FileId = fileResponse.Id, + Usage = "chatroom.background", + ResourceId = chatRoom.ResourceIdentifier, + }); + } + catch (RpcException ex) when (ex.StatusCode == Grpc.Core.StatusCode.NotFound) + { return BadRequest("Invalid background id, unable to find the file on cloud."); + } } db.ChatRooms.Add(chatRoom); @@ -225,23 +255,33 @@ public class ChatRoomController( var chatRoomResourceId = $"chatroom:{chatRoom.Id}"; if (chatRoom.Picture is not null) - await fileRefService.CreateReferenceAsync( - chatRoom.Picture.Id, - "chat.room.picture", - chatRoomResourceId - ); + { + await fileRefs.CreateReferenceAsync(new CreateReferenceRequest + { + FileId = chatRoom.Picture.Id, + Usage = "chat.room.picture", + ResourceId = chatRoomResourceId + }); + } if (chatRoom.Background is not null) - await fileRefService.CreateReferenceAsync( - chatRoom.Background.Id, - "chat.room.background", - chatRoomResourceId - ); + { + await fileRefs.CreateReferenceAsync(new CreateReferenceRequest + { + FileId = chatRoom.Background.Id, + Usage = "chat.room.background", + ResourceId = chatRoomResourceId + }); + } - als.CreateActionLogFromRequest( - ActionLogType.ChatroomCreate, - new Dictionary { { "chatroom_id", chatRoom.Id } }, Request - ); + _ = als.CreateActionLogAsync(new CreateActionLogRequest + { + Action = "chatrooms.create", + Meta = { { "chatroom_id", Google.Protobuf.WellKnownTypes.Value.ForString(chatRoom.Id.ToString()) } }, + AccountId = currentUser.Id, + UserAgent = Request.Headers.UserAgent, + IpAddress = Request.HttpContext.Connection.RemoteIpAddress?.ToString() + }); return Ok(chatRoom); } @@ -279,38 +319,62 @@ public class ChatRoomController( if (request.PictureId is not null) { - var picture = await db.Files.FindAsync(request.PictureId); - if (picture is null) return BadRequest("Invalid picture id, unable to find the file on cloud."); + try + { + var fileResponse = await files.GetFileAsync(new GetFileRequest { Id = request.PictureId }); + if (fileResponse == null) return BadRequest("Invalid picture id, unable to find the file on cloud."); - // Remove old references for pictures - await fileRefService.DeleteResourceReferencesAsync(chatRoom.ResourceIdentifier, "chat.room.picture"); + // Remove old references for pictures + await fileRefs.DeleteResourceReferencesAsync(new DeleteResourceReferencesRequest + { + ResourceId = chatRoom.ResourceIdentifier, + Usage = "chat.room.picture" + }); - // Add a new reference - await fileRefService.CreateReferenceAsync( - picture.Id, - "chat.room.picture", - chatRoom.ResourceIdentifier - ); + // Add a new reference + await fileRefs.CreateReferenceAsync(new CreateReferenceRequest + { + FileId = fileResponse.Id, + Usage = "chat.room.picture", + ResourceId = chatRoom.ResourceIdentifier + }); - chatRoom.Picture = picture.ToReferenceObject(); + chatRoom.Picture = CloudFileReferenceObject.FromProtoValue(fileResponse); + } + catch (RpcException ex) when (ex.StatusCode == Grpc.Core.StatusCode.NotFound) + { + return BadRequest("Invalid picture id, unable to find the file on cloud."); + } } if (request.BackgroundId is not null) { - var background = await db.Files.FindAsync(request.BackgroundId); - if (background is null) return BadRequest("Invalid background id, unable to find the file on cloud."); + try + { + var fileResponse = await files.GetFileAsync(new GetFileRequest { Id = request.BackgroundId }); + if (fileResponse == null) return BadRequest("Invalid background id, unable to find the file on cloud."); - // Remove old references for backgrounds - await fileRefService.DeleteResourceReferencesAsync(chatRoom.ResourceIdentifier, "chat.room.background"); + // Remove old references for backgrounds + await fileRefs.DeleteResourceReferencesAsync(new DeleteResourceReferencesRequest + { + ResourceId = chatRoom.ResourceIdentifier, + Usage = "chat.room.background" + }); - // Add a new reference - await fileRefService.CreateReferenceAsync( - background.Id, - "chat.room.background", - chatRoom.ResourceIdentifier - ); + // Add a new reference + await fileRefs.CreateReferenceAsync(new CreateReferenceRequest + { + FileId = fileResponse.Id, + Usage = "chat.room.background", + ResourceId = chatRoom.ResourceIdentifier + }); - chatRoom.Background = background.ToReferenceObject(); + chatRoom.Background = CloudFileReferenceObject.FromProtoValue(fileResponse); + } + catch (RpcException ex) when (ex.StatusCode == Grpc.Core.StatusCode.NotFound) + { + return BadRequest("Invalid background id, unable to find the file on cloud."); + } } if (request.Name is not null) @@ -325,10 +389,14 @@ public class ChatRoomController( db.ChatRooms.Update(chatRoom); await db.SaveChangesAsync(); - als.CreateActionLogFromRequest( - ActionLogType.ChatroomUpdate, - new Dictionary { { "chatroom_id", chatRoom.Id } }, Request - ); + _ = als.CreateActionLogAsync(new CreateActionLogRequest + { + Action = "chatrooms.update", + Meta = { { "chatroom_id", Google.Protobuf.WellKnownTypes.Value.ForString(chatRoom.Id.ToString()) } }, + AccountId = currentUser.Id, + UserAgent = Request.Headers.UserAgent, + IpAddress = Request.HttpContext.Connection.RemoteIpAddress?.ToString() + }); return Ok(chatRoom); } @@ -355,15 +423,22 @@ public class ChatRoomController( var chatRoomResourceId = $"chatroom:{chatRoom.Id}"; // Delete all file references for this chat room - await fileRefService.DeleteResourceReferencesAsync(chatRoomResourceId); + await fileRefs.DeleteResourceReferencesAsync(new DeleteResourceReferencesRequest + { + ResourceId = chatRoomResourceId + }); db.ChatRooms.Remove(chatRoom); await db.SaveChangesAsync(); - als.CreateActionLogFromRequest( - ActionLogType.ChatroomDelete, - new Dictionary { { "chatroom_id", chatRoom.Id } }, Request - ); + _ = als.CreateActionLogAsync(new CreateActionLogRequest + { + Action = "chatrooms.delete", + Meta = { { "chatroom_id", Google.Protobuf.WellKnownTypes.Value.ForString(chatRoom.Id.ToString()) } }, + AccountId = currentUser.Id, + UserAgent = Request.Headers.UserAgent, + IpAddress = Request.HttpContext.Connection.RemoteIpAddress?.ToString() + }); return NoContent(); } @@ -411,44 +486,44 @@ public class ChatRoomController( .Include(m => m.Account) .Include(m => m.Account.Profile); - if (withStatus) - { - var members = await query - .OrderBy(m => m.JoinedAt) - .ToListAsync(); + // if (withStatus) + // { + // var members = await query + // .OrderBy(m => m.JoinedAt) + // .ToListAsync(); + // + // var memberStatuses = await aes.GetStatuses(members.Select(m => m.AccountId).ToList()); + // + // if (!string.IsNullOrEmpty(status)) + // { + // members = members.Where(m => + // memberStatuses.TryGetValue(m.AccountId, out var s) && s.Label != null && + // s.Label.Equals(status, StringComparison.OrdinalIgnoreCase)).ToList(); + // } + // + // members = members.OrderByDescending(m => memberStatuses.TryGetValue(m.AccountId, out var s) && s.IsOnline) + // .ToList(); + // + // var total = members.Count; + // Response.Headers.Append("X-Total", total.ToString()); + // + // var result = members.Skip(skip).Take(take).ToList(); + // + // return Ok(result); + // } + // else + // { + var total = await query.CountAsync(); + Response.Headers.Append("X-Total", total.ToString()); - var memberStatuses = await aes.GetStatuses(members.Select(m => m.AccountId).ToList()); + var members = await query + .OrderBy(m => m.JoinedAt) + .Skip(skip) + .Take(take) + .ToListAsync(); - if (!string.IsNullOrEmpty(status)) - { - members = members.Where(m => - memberStatuses.TryGetValue(m.AccountId, out var s) && s.Label != null && - s.Label.Equals(status, StringComparison.OrdinalIgnoreCase)).ToList(); - } - - members = members.OrderByDescending(m => memberStatuses.TryGetValue(m.AccountId, out var s) && s.IsOnline) - .ToList(); - - var total = members.Count; - Response.Headers.Append("X-Total", total.ToString()); - - var result = members.Skip(skip).Take(take).ToList(); - - return Ok(result); - } - else - { - var total = await query.CountAsync(); - Response.Headers.Append("X-Total", total.ToString()); - - var members = await query - .OrderBy(m => m.JoinedAt) - .Skip(skip) - .Take(take) - .ToListAsync(); - - return Ok(members); - } + return Ok(members); + // } } @@ -463,14 +538,23 @@ public class ChatRoomController( public async Task> InviteMember(Guid roomId, [FromBody] ChatMemberRequest request) { - if (HttpContext.Items["CurrentUser"] is not Shared.Proto.Account currentUser) return Unauthorized(); + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); var accountId = Guid.Parse(currentUser.Id); - var relatedUser = await db.Accounts.FindAsync(request.RelatedUserId); - if (relatedUser is null) return BadRequest("Related user was not found"); + // Get related user account + var relatedUser = + await accounts.GetAccountAsync(new GetAccountRequest { Id = request.RelatedUserId.ToString() }); + if (relatedUser == null) return BadRequest("Related user was not found"); - if (await rels.HasRelationshipWithStatus(Guid.Parse(currentUser.Id), relatedUser.Id, - RelationshipStatus.Blocked)) + // Check if the user has blocked the current user + var relationship = await accounts.GetRelationshipAsync(new GetRelationshipRequest + { + AccountId = currentUser.Id, + RelatedId = relatedUser.Id, + Status = -100 + }); + + if (relationship != null && relationship.Relationship.Status == -100) return StatusCode(403, "You cannot invite a user that blocked you."); var chatRoom = await db.ChatRooms @@ -512,7 +596,7 @@ public class ChatRoomController( var newMember = new ChatMember { - AccountId = relatedUser.Id, + AccountId = Guid.Parse(relatedUser.Id), ChatRoomId = roomId, Role = request.Role, }; @@ -523,10 +607,18 @@ public class ChatRoomController( newMember.ChatRoom = chatRoom; await _SendInviteNotify(newMember, currentUser); - als.CreateActionLogFromRequest( - ActionLogType.ChatroomInvite, - new Dictionary { { "chatroom_id", chatRoom.Id }, { "account_id", relatedUser.Id } }, Request - ); + _ = als.CreateActionLogAsync(new CreateActionLogRequest + { + Action = "chatrooms.invite", + Meta = + { + { "chatroom_id", Google.Protobuf.WellKnownTypes.Value.ForString(chatRoom.Id.ToString()) }, + { "account_id", Google.Protobuf.WellKnownTypes.Value.ForString(relatedUser.Id.ToString()) } + }, + AccountId = currentUser.Id, + UserAgent = Request.Headers.UserAgent, + IpAddress = Request.HttpContext.Connection.RemoteIpAddress?.ToString() + }); return Ok(newMember); } @@ -575,10 +667,14 @@ public class ChatRoomController( await db.SaveChangesAsync(); _ = crs.PurgeRoomMembersCache(roomId); - als.CreateActionLogFromRequest( - ActionLogType.ChatroomJoin, - new Dictionary { { "chatroom_id", roomId } }, Request - ); + _ = als.CreateActionLogAsync(new CreateActionLogRequest + { + Action = ActionLogType.ChatroomJoin, + Meta = { { "chatroom_id", Google.Protobuf.WellKnownTypes.Value.ForString(roomId.ToString()) } }, + AccountId = currentUser.Id, + UserAgent = Request.Headers.UserAgent, + IpAddress = Request.HttpContext.Connection.RemoteIpAddress?.ToString() + }); return Ok(member); } @@ -688,12 +784,19 @@ public class ChatRoomController( await crs.PurgeRoomMembersCache(roomId); - als.CreateActionLogFromRequest( - ActionLogType.RealmAdjustRole, - new Dictionary - { { "chatroom_id", roomId }, { "account_id", memberId }, { "new_role", newRole } }, - Request - ); + _ = als.CreateActionLogAsync(new CreateActionLogRequest + { + Action = "chatrooms.role.edit", + Meta = + { + { "chatroom_id", Google.Protobuf.WellKnownTypes.Value.ForString(roomId.ToString()) }, + { "account_id", Google.Protobuf.WellKnownTypes.Value.ForString(memberId.ToString()) }, + { "new_role", Google.Protobuf.WellKnownTypes.Value.ForNumber(newRole) } + }, + AccountId = currentUser.Id, + UserAgent = Request.Headers.UserAgent, + IpAddress = Request.HttpContext.Connection.RemoteIpAddress?.ToString() + }); return Ok(targetMember); } @@ -738,10 +841,18 @@ public class ChatRoomController( await db.SaveChangesAsync(); _ = crs.PurgeRoomMembersCache(roomId); - als.CreateActionLogFromRequest( - ActionLogType.ChatroomKick, - new Dictionary { { "chatroom_id", roomId }, { "account_id", memberId } }, Request - ); + _ = als.CreateActionLogAsync(new CreateActionLogRequest + { + Action = "chatrooms.kick", + Meta = + { + { "chatroom_id", Google.Protobuf.WellKnownTypes.Value.ForString(roomId.ToString()) }, + { "account_id", Google.Protobuf.WellKnownTypes.Value.ForString(memberId.ToString()) } + }, + AccountId = currentUser.Id, + UserAgent = Request.Headers.UserAgent, + IpAddress = Request.HttpContext.Connection.RemoteIpAddress?.ToString() + }); return NoContent(); } @@ -780,10 +891,14 @@ public class ChatRoomController( await db.SaveChangesAsync(); _ = crs.PurgeRoomMembersCache(roomId); - als.CreateActionLogFromRequest( - ActionLogType.ChatroomJoin, - new Dictionary { { "chatroom_id", roomId } }, Request - ); + _ = als.CreateActionLogAsync(new CreateActionLogRequest + { + Action = ActionLogType.ChatroomJoin, + Meta = { { "chatroom_id", Google.Protobuf.WellKnownTypes.Value.ForString(roomId.ToString()) } }, + AccountId = currentUser.Id, + UserAgent = Request.Headers.UserAgent, + IpAddress = Request.HttpContext.Connection.RemoteIpAddress?.ToString() + }); return Ok(chatRoom); } @@ -817,10 +932,14 @@ public class ChatRoomController( await db.SaveChangesAsync(); await crs.PurgeRoomMembersCache(roomId); - als.CreateActionLogFromRequest( - ActionLogType.ChatroomLeave, - new Dictionary { { "chatroom_id", roomId } }, Request - ); + _ = als.CreateActionLogAsync(new CreateActionLogRequest + { + Action = ActionLogType.ChatroomLeave, + Meta = { { "chatroom_id", Google.Protobuf.WellKnownTypes.Value.ForString(roomId.ToString()) } }, + AccountId = currentUser.Id, + UserAgent = Request.Headers.UserAgent, + IpAddress = Request.HttpContext.Connection.RemoteIpAddress?.ToString() + }); return NoContent(); } @@ -833,7 +952,19 @@ public class ChatRoomController( ? localizer["ChatInviteDirectBody", sender.Nick] : localizer["ChatInviteBody", member.ChatRoom.Name ?? "Unnamed"]; - AccountService.SetCultureInfo(member.Account); - await nty.SendNotification(member.Account, "invites.chats", title, null, body, actionUri: "/chat"); + CultureService.SetCultureInfo(member.Account); + await pusher.SendPushNotificationToUserAsync( + new SendPushNotificationToUserRequest + { + UserId = member.Account.Id.ToString(), + Notification = new PushNotification + { + Topic = "invites.chats", + Title = title, + Body = body, + IsSavable = false + } + } + ); } } \ No newline at end of file diff --git a/DysonNetwork.Sphere/Chat/ChatRoomController.cs.bak b/DysonNetwork.Sphere/Chat/ChatRoomController.cs.bak new file mode 100644 index 0000000..f5d702c --- /dev/null +++ b/DysonNetwork.Sphere/Chat/ChatRoomController.cs.bak @@ -0,0 +1,947 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using System.ComponentModel.DataAnnotations; +using DysonNetwork.Shared.Data; +using DysonNetwork.Shared.Proto; +using DysonNetwork.Sphere.Localization; +using DysonNetwork.Sphere.Permission; +using DysonNetwork.Sphere.Realm; +using Grpc.Core; +using Microsoft.AspNetCore.Authorization; +using Microsoft.Extensions.Localization; +using NodaTime; + +namespace DysonNetwork.Sphere.Chat; + +[ApiController] +[Route("/api/chat")] +public class ChatRoomController( + AppDatabase db, + ChatRoomService crs, + RealmService rs, + IStringLocalizer localizer, + AccountService.AccountServiceClient accounts, + FileService.FileServiceClient files, + FileReferenceService.FileReferenceServiceClient fileRefs, + ActionLogService.ActionLogServiceClient als +) : ControllerBase +{ + [HttpGet("{id:guid}")] + public async Task> GetChatRoom(Guid id) + { + var chatRoom = await db.ChatRooms + .Where(c => c.Id == id) + .Include(e => e.Realm) + .FirstOrDefaultAsync(); + if (chatRoom is null) return NotFound(); + if (chatRoom.Type != ChatRoomType.DirectMessage) return Ok(chatRoom); + + if (HttpContext.Items["CurrentUser"] is Account currentUser) + chatRoom = await crs.LoadDirectMessageMembers(chatRoom, Guid.Parse(currentUser.Id)); + + return Ok(chatRoom); + } + + [HttpGet] + [Authorize] + public async Task>> ListJoinedChatRooms() + { + if (HttpContext.Items["CurrentUser"] is not Account currentUser) + return Unauthorized(); + var accountId = Guid.Parse(currentUser.Id); + + var chatRooms = await db.ChatMembers + .Where(m => m.AccountId == accountId) + .Where(m => m.JoinedAt != null) + .Where(m => m.LeaveAt == null) + .Include(m => m.ChatRoom) + .Select(m => m.ChatRoom) + .ToListAsync(); + chatRooms = await crs.LoadDirectMessageMembers(chatRooms, accountId); + chatRooms = await crs.SortChatRoomByLastMessage(chatRooms); + + return Ok(chatRooms); + } + + public class DirectMessageRequest + { + [Required] public Guid RelatedUserId { get; set; } + } + + [HttpPost("direct")] + [Authorize] + public async Task> CreateDirectMessage([FromBody] DirectMessageRequest request) + { + if (HttpContext.Items["CurrentUser"] is not Shared.Proto.Account currentUser) + return Unauthorized(); + + var relatedUser = await accounts.GetAccountAsync( + new GetAccountRequest { Id = request.RelatedUserId.ToString() } + ); + if (relatedUser is null) + return BadRequest("Related user was not found"); + + var hasBlocked = await accounts.HasRelationshipAsync(new GetRelationshipRequest() + { + AccountId = currentUser.Id, + RelatedId = request.RelatedUserId.ToString(), + Status = -100 + }); + if (hasBlocked?.Value ?? false) + return StatusCode(403, "You cannot create direct message with a user that blocked you."); + + // Check if DM already exists between these users + var existingDm = await db.ChatRooms + .Include(c => c.Members) + .Where(c => c.Type == ChatRoomType.DirectMessage && c.Members.Count == 2) + .Where(c => c.Members.Any(m => m.AccountId == Guid.Parse(currentUser.Id))) + .Where(c => c.Members.Any(m => m.AccountId == request.RelatedUserId)) + .FirstOrDefaultAsync(); + + if (existingDm != null) + return BadRequest("You already have a DM with this user."); + + // Create new DM chat room + var dmRoom = new ChatRoom + { + Type = ChatRoomType.DirectMessage, + IsPublic = false, + Members = new List + { + new() + { + AccountId = Guid.Parse(currentUser.Id), + Role = ChatMemberRole.Owner, + JoinedAt = Instant.FromDateTimeUtc(DateTime.UtcNow) + }, + new() + { + AccountId = request.RelatedUserId, + Role = ChatMemberRole.Member, + JoinedAt = null, // Pending status + } + } + }; + + db.ChatRooms.Add(dmRoom); + await db.SaveChangesAsync(); + + _ = als.CreateActionLogAsync(new CreateActionLogRequest + { + Action = "chatrooms.create", + Meta = { { "chatroom_id", Google.Protobuf.WellKnownTypes.Value.ForString(dmRoom.Id.ToString()) } }, + AccountId = currentUser.Id, + UserAgent = Request.Headers.UserAgent, + IpAddress = Request.HttpContext.Connection.RemoteIpAddress?.ToString() + }); + + var invitedMember = dmRoom.Members.First(m => m.AccountId == request.RelatedUserId); + invitedMember.ChatRoom = dmRoom; + await _SendInviteNotify(invitedMember, currentUser); + + return Ok(dmRoom); + } + + [HttpGet("direct/{accountId:guid}")] + [Authorize] + public async Task> GetDirectChatRoom(Guid accountId) + { + if (HttpContext.Items["CurrentUser"] is not Shared.Proto.Account currentUser) + return Unauthorized(); + + var room = await db.ChatRooms + .Include(c => c.Members) + .Where(c => c.Type == ChatRoomType.DirectMessage && c.Members.Count == 2) + .Where(c => c.Members.Any(m => m.AccountId == Guid.Parse(currentUser.Id))) + .Where(c => c.Members.Any(m => m.AccountId == accountId)) + .FirstOrDefaultAsync(); + if (room is null) return NotFound(); + + return Ok(room); + } + + public class ChatRoomRequest + { + [Required] [MaxLength(1024)] public string? Name { get; set; } + [MaxLength(4096)] public string? Description { get; set; } + [MaxLength(32)] public string? PictureId { get; set; } + [MaxLength(32)] public string? BackgroundId { get; set; } + public Guid? RealmId { get; set; } + public bool? IsCommunity { get; set; } + public bool? IsPublic { get; set; } + } + + [HttpPost] + [Authorize] + [RequiredPermission("global", "chat.create")] + public async Task> CreateChatRoom(ChatRoomRequest request) + { + if (HttpContext.Items["CurrentUser"] is not Shared.Proto.Account currentUser) return Unauthorized(); + if (request.Name is null) return BadRequest("You cannot create a chat room without a name."); + + var chatRoom = new ChatRoom + { + Name = request.Name, + Description = request.Description ?? string.Empty, + IsCommunity = request.IsCommunity ?? false, + IsPublic = request.IsPublic ?? false, + Type = ChatRoomType.Group, + Members = new List + { + new() + { + Role = ChatMemberRole.Owner, + AccountId = Guid.Parse(currentUser.Id), + JoinedAt = NodaTime.Instant.FromDateTimeUtc(DateTime.UtcNow) + } + } + }; + + if (request.RealmId is not null) + { + if (!await rs.IsMemberWithRole(request.RealmId.Value, Guid.Parse(currentUser.Id), + RealmMemberRole.Moderator)) + return StatusCode(403, "You need at least be a moderator to create chat linked to the realm."); + chatRoom.RealmId = request.RealmId; + } + + if (request.PictureId is not null) + { + try + { + var fileResponse = await files.GetFileAsync(new GetFileRequest { Id = request.PictureId }); + if (fileResponse == null) return BadRequest("Invalid picture id, unable to find the file on cloud."); + chatRoom.Picture = CloudFileReferenceObject.FromProtoValue(fileResponse); + + await fileRefs.CreateReferenceAsync(new CreateReferenceRequest + { + FileId = fileResponse.Id, + Usage = "chatroom.picture", + ResourceId = chatRoom.ResourceIdentifier, + }); + } + catch (RpcException ex) when (ex.StatusCode == Grpc.Core.StatusCode.NotFound) + { + return BadRequest("Invalid picture id, unable to find the file on cloud."); + } + } + + if (request.BackgroundId is not null) + { + try + { + var fileResponse = await files.GetFileAsync(new GetFileRequest { Id = request.BackgroundId }); + if (fileResponse == null) return BadRequest("Invalid background id, unable to find the file on cloud."); + chatRoom.Background = CloudFileReferenceObject.FromProtoValue(fileResponse); + + await fileRefs.CreateReferenceAsync(new CreateReferenceRequest + { + FileId = fileResponse.Id, + Usage = "chatroom.background", + ResourceId = chatRoom.ResourceIdentifier, + }); + } + catch (RpcException ex) when (ex.StatusCode == Grpc.Core.StatusCode.NotFound) + { + return BadRequest("Invalid background id, unable to find the file on cloud."); + } + } + + db.ChatRooms.Add(chatRoom); + await db.SaveChangesAsync(); + + var chatRoomResourceId = $"chatroom:{chatRoom.Id}"; + + if (chatRoom.Picture is not null) + { + await fileRefs.CreateReferenceAsync(new CreateReferenceRequest + { + FileId = chatRoom.Picture.Id, + Usage = "chat.room.picture", + ResourceId = chatRoomResourceId + }); + } + + if (chatRoom.Background is not null) + { + await fileRefs.CreateReferenceAsync(new CreateReferenceRequest + { + FileId = chatRoom.Background.Id, + Usage = "chat.room.background", + ResourceId = chatRoomResourceId + }); + } + + _ = als.CreateActionLogAsync(new CreateActionLogRequest + { + Action = "chatrooms.create", + Meta = { { "chatroom_id", Google.Protobuf.WellKnownTypes.Value.ForString(chatRoom.Id.ToString()) } }, + AccountId = currentUser.Id, + UserAgent = Request.Headers.UserAgent, + IpAddress = Request.HttpContext.Connection.RemoteIpAddress?.ToString() + }); + + return Ok(chatRoom); + } + + + [HttpPatch("{id:guid}")] + public async Task> UpdateChatRoom(Guid id, [FromBody] ChatRoomRequest request) + { + if (HttpContext.Items["CurrentUser"] is not Shared.Proto.Account currentUser) return Unauthorized(); + + var chatRoom = await db.ChatRooms + .Where(e => e.Id == id) + .FirstOrDefaultAsync(); + if (chatRoom is null) return NotFound(); + + if (chatRoom.RealmId is not null) + { + if (!await rs.IsMemberWithRole(chatRoom.RealmId.Value, Guid.Parse(currentUser.Id), + RealmMemberRole.Moderator)) + return StatusCode(403, "You need at least be a realm moderator to update the chat."); + } + else if (!await crs.IsMemberWithRole(chatRoom.Id, Guid.Parse(currentUser.Id), ChatMemberRole.Moderator)) + return StatusCode(403, "You need at least be a moderator to update the chat."); + + if (request.RealmId is not null) + { + var member = await db.RealmMembers + .Where(m => m.AccountId == Guid.Parse(currentUser.Id)) + .Where(m => m.RealmId == request.RealmId) + .FirstOrDefaultAsync(); + if (member is null || member.Role < RealmMemberRole.Moderator) + return StatusCode(403, "You need at least be a moderator to transfer the chat linked to the realm."); + chatRoom.RealmId = member.RealmId; + } + + if (request.PictureId is not null) + { + try + { + var fileResponse = await files.GetFileAsync(new GetFileRequest { Id = request.PictureId }); + if (fileResponse == null) return BadRequest("Invalid picture id, unable to find the file on cloud."); + + // Remove old references for pictures + await fileRefs.DeleteResourceReferencesAsync(new DeleteResourceReferencesRequest + { + ResourceId = chatRoom.ResourceIdentifier, + Usage = "chat.room.picture" + }); + + // Add a new reference + await fileRefs.CreateReferenceAsync(new CreateReferenceRequest + { + FileId = fileResponse.Id, + Usage = "chat.room.picture", + ResourceId = chatRoom.ResourceIdentifier + }); + + chatRoom.Picture = CloudFileReferenceObject.FromProtoValue(fileResponse); + } + catch (RpcException ex) when (ex.StatusCode == Grpc.Core.StatusCode.NotFound) + { + return BadRequest("Invalid picture id, unable to find the file on cloud."); + } + } + + if (request.BackgroundId is not null) + { + try + { + var fileResponse = await files.GetFileAsync(new GetFileRequest { Id = request.BackgroundId }); + if (fileResponse == null) return BadRequest("Invalid background id, unable to find the file on cloud."); + + // Remove old references for backgrounds + await fileRefs.DeleteResourceReferencesAsync(new DeleteResourceReferencesRequest + { + ResourceId = chatRoom.ResourceIdentifier, + Usage = "chat.room.background" + }); + + // Add a new reference + await fileRefs.CreateReferenceAsync(new CreateReferenceRequest + { + FileId = fileResponse.Id, + Usage = "chat.room.background", + ResourceId = chatRoom.ResourceIdentifier + }); + + chatRoom.Background = CloudFileReferenceObject.FromProtoValue(fileResponse); + } + catch (RpcException ex) when (ex.StatusCode == Grpc.Core.StatusCode.NotFound) + { + return BadRequest("Invalid background id, unable to find the file on cloud."); + } + } + + if (request.Name is not null) + chatRoom.Name = request.Name; + if (request.Description is not null) + chatRoom.Description = request.Description; + if (request.IsCommunity is not null) + chatRoom.IsCommunity = request.IsCommunity.Value; + if (request.IsPublic is not null) + chatRoom.IsPublic = request.IsPublic.Value; + + db.ChatRooms.Update(chatRoom); + await db.SaveChangesAsync(); + + _ = als.CreateActionLogAsync(new CreateActionLogRequest + { + Action = "chatrooms.update", + Meta = { { "chatroom_id", Google.Protobuf.WellKnownTypes.Value.ForString(chatRoom.Id.ToString()) } }, + AccountId = currentUser.Id, + UserAgent = Request.Headers.UserAgent, + IpAddress = Request.HttpContext.Connection.RemoteIpAddress?.ToString() + }); + + return Ok(chatRoom); + } + + [HttpDelete("{id:guid}")] + public async Task DeleteChatRoom(Guid id) + { + if (HttpContext.Items["CurrentUser"] is not Shared.Proto.Account currentUser) return Unauthorized(); + + var chatRoom = await db.ChatRooms + .Where(e => e.Id == id) + .FirstOrDefaultAsync(); + if (chatRoom is null) return NotFound(); + + if (chatRoom.RealmId is not null) + { + if (!await rs.IsMemberWithRole(chatRoom.RealmId.Value, Guid.Parse(currentUser.Id), + RealmMemberRole.Moderator)) + return StatusCode(403, "You need at least be a realm moderator to delete the chat."); + } + else if (!await crs.IsMemberWithRole(chatRoom.Id, Guid.Parse(currentUser.Id), ChatMemberRole.Owner)) + return StatusCode(403, "You need at least be the owner to delete the chat."); + + var chatRoomResourceId = $"chatroom:{chatRoom.Id}"; + + // Delete all file references for this chat room + await fileRefs.DeleteResourceReferencesAsync(new DeleteResourceReferencesRequest + { + ResourceId = chatRoomResourceId + }); + + db.ChatRooms.Remove(chatRoom); + await db.SaveChangesAsync(); + + _ = als.CreateActionLogAsync(new CreateActionLogRequest + { + Action = "chatrooms.delete", + Meta = { { "chatroom_id", Google.Protobuf.WellKnownTypes.Value.ForString(chatRoom.Id.ToString()) } }, + AccountId = currentUser.Id, + UserAgent = Request.Headers.UserAgent, + IpAddress = Request.HttpContext.Connection.RemoteIpAddress?.ToString() + }); + + return NoContent(); + } + + [HttpGet("{roomId:guid}/members/me")] + [Authorize] + public async Task> GetRoomIdentity(Guid roomId) + { + if (HttpContext.Items["CurrentUser"] is not Shared.Proto.Account currentUser) + return Unauthorized(); + + var member = await db.ChatMembers + .Where(m => m.AccountId == Guid.Parse(currentUser.Id) && m.ChatRoomId == roomId) + .Include(m => m.Account) + .Include(m => m.Account.Profile) + .FirstOrDefaultAsync(); + + if (member == null) + return NotFound(); + + return Ok(member); + } + + [HttpGet("{roomId:guid}/members")] + public async Task>> ListMembers(Guid roomId, [FromQuery] int take = 20, + [FromQuery] int skip = 0, [FromQuery] bool withStatus = false, [FromQuery] string? status = null) + { + var currentUser = HttpContext.Items["CurrentUser"] as Shared.Proto.Account; + + var room = await db.ChatRooms + .FirstOrDefaultAsync(r => r.Id == roomId); + if (room is null) return NotFound(); + + if (!room.IsPublic) + { + if (currentUser is null) return Unauthorized(); + var member = await db.ChatMembers + .FirstOrDefaultAsync(m => m.ChatRoomId == roomId && m.AccountId == Guid.Parse(currentUser.Id)); + if (member is null) return StatusCode(403, "You need to be a member to see members of private chat room."); + } + + IQueryable query = db.ChatMembers + .Where(m => m.ChatRoomId == roomId) + .Where(m => m.LeaveAt == null) // Add this condition to exclude left members + .Include(m => m.Account) + .Include(m => m.Account.Profile); + + // if (withStatus) + // { + // var members = await query + // .OrderBy(m => m.JoinedAt) + // .ToListAsync(); + // + // var memberStatuses = await aes.GetStatuses(members.Select(m => m.AccountId).ToList()); + // + // if (!string.IsNullOrEmpty(status)) + // { + // members = members.Where(m => + // memberStatuses.TryGetValue(m.AccountId, out var s) && s.Label != null && + // s.Label.Equals(status, StringComparison.OrdinalIgnoreCase)).ToList(); + // } + // + // members = members.OrderByDescending(m => memberStatuses.TryGetValue(m.AccountId, out var s) && s.IsOnline) + // .ToList(); + // + // var total = members.Count; + // Response.Headers.Append("X-Total", total.ToString()); + // + // var result = members.Skip(skip).Take(take).ToList(); + // + // return Ok(result); + // } + // else + // { + var total = await query.CountAsync(); + Response.Headers.Append("X-Total", total.ToString()); + + var members = await query + .OrderBy(m => m.JoinedAt) + .Skip(skip) + .Take(take) + .ToListAsync(); + + return Ok(members); + // } + } + + + public class ChatMemberRequest + { + [Required] public Guid RelatedUserId { get; set; } + [Required] public int Role { get; set; } + } + + [HttpPost("invites/{roomId:guid}")] + [Authorize] + public async Task> InviteMember(Guid roomId, + [FromBody] ChatMemberRequest request) + { + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); + var accountId = Guid.Parse(currentUser.Id); + + // Get related user account + var relatedUser = await accounts.GetAccountAsync(new GetAccountRequest { Id = request.RelatedUserId.ToString() }); + if (relatedUser == null) return BadRequest("Related user was not found"); + + // Check if the user has blocked the current user + var relationship = await accounts.GetRelationshipAsync(new GetRelationshipRequest + { + AccountId = currentUser.Id, + RelatedId = relatedUser.Id, + Status = -100 + }); + + if (relationship != null && relationship.Relationship.Status == -100) + return StatusCode(403, "You cannot invite a user that blocked you."); + + var chatRoom = await db.ChatRooms + .Where(p => p.Id == roomId) + .FirstOrDefaultAsync(); + if (chatRoom is null) return NotFound(); + + // Handle realm-owned chat rooms + if (chatRoom.RealmId is not null) + { + var realmMember = await db.RealmMembers + .Where(m => m.AccountId == accountId) + .Where(m => m.RealmId == chatRoom.RealmId) + .FirstOrDefaultAsync(); + if (realmMember is null || realmMember.Role < RealmMemberRole.Moderator) + return StatusCode(403, "You need at least be a realm moderator to invite members to this chat."); + } + else + { + var chatMember = await db.ChatMembers + .Where(m => m.AccountId == accountId) + .Where(m => m.ChatRoomId == roomId) + .FirstOrDefaultAsync(); + if (chatMember is null) return StatusCode(403, "You are not even a member of the targeted chat room."); + if (chatMember.Role < ChatMemberRole.Moderator) + return StatusCode(403, + "You need at least be a moderator to invite other members to this chat room."); + if (chatMember.Role < request.Role) + return StatusCode(403, "You cannot invite member with higher permission than yours."); + } + + var hasExistingMember = await db.ChatMembers + .Where(m => m.AccountId == request.RelatedUserId) + .Where(m => m.ChatRoomId == roomId) + .Where(m => m.LeaveAt == null) + .AnyAsync(); + if (hasExistingMember) + return BadRequest("This user has been joined the chat cannot be invited again."); + + var newMember = new ChatMember + { + AccountId = Guid.Parse(relatedUser.Id), + ChatRoomId = roomId, + Role = request.Role, + }; + + db.ChatMembers.Add(newMember); + await db.SaveChangesAsync(); + + newMember.ChatRoom = chatRoom; + await _SendInviteNotify(newMember, currentUser); + + _ = als.CreateActionLogAsync(new CreateActionLogRequest + { + Action = "chatrooms.invite", + Meta = + { + { "chatroom_id", Google.Protobuf.WellKnownTypes.Value.ForString(chatRoom.Id.ToString()) }, + { "account_id", Google.Protobuf.WellKnownTypes.Value.ForString(relatedUser.Id.ToString()) } + }, + AccountId = currentUser.Id, + UserAgent = Request.Headers.UserAgent, + IpAddress = Request.HttpContext.Connection.RemoteIpAddress?.ToString() + }); + + return Ok(newMember); + } + + [HttpGet("invites")] + [Authorize] + public async Task>> ListChatInvites() + { + if (HttpContext.Items["CurrentUser"] is not Shared.Proto.Account currentUser) return Unauthorized(); + var accountId = Guid.Parse(currentUser.Id); + + var members = await db.ChatMembers + .Where(m => m.AccountId == accountId) + .Where(m => m.JoinedAt == null) + .Include(e => e.ChatRoom) + .Include(e => e.Account) + .Include(e => e.Account.Profile) + .ToListAsync(); + + var chatRooms = members.Select(m => m.ChatRoom).ToList(); + var directMembers = + (await crs.LoadDirectMessageMembers(chatRooms, accountId)).ToDictionary(c => c.Id, c => c.Members); + + foreach (var member in members.Where(member => member.ChatRoom.Type == ChatRoomType.DirectMessage)) + member.ChatRoom.Members = directMembers[member.ChatRoom.Id]; + + return members.ToList(); + } + + [HttpPost("invites/{roomId:guid}/accept")] + [Authorize] + public async Task> AcceptChatInvite(Guid roomId) + { + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); + var accountId = Guid.Parse(currentUser.Id); + + var member = await db.ChatMembers + .Where(m => m.AccountId == accountId) + .Where(m => m.ChatRoomId == roomId) + .Where(m => m.JoinedAt == null) + .FirstOrDefaultAsync(); + if (member is null) return NotFound(); + + member.JoinedAt = Instant.FromDateTimeUtc(DateTime.UtcNow); + db.Update(member); + await db.SaveChangesAsync(); + _ = crs.PurgeRoomMembersCache(roomId); + + als.CreateActionLogFromRequest( + ActionLogType.ChatroomJoin, + new Dictionary { { "chatroom_id", roomId } }, Request + ); + + return Ok(member); + } + + [HttpPost("invites/{roomId:guid}/decline")] + [Authorize] + public async Task DeclineChatInvite(Guid roomId) + { + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); + var accountId = Guid.Parse(currentUser.Id); + + var member = await db.ChatMembers + .Where(m => m.AccountId == accountId) + .Where(m => m.ChatRoomId == roomId) + .Where(m => m.JoinedAt == null) + .FirstOrDefaultAsync(); + if (member is null) return NotFound(); + + member.LeaveAt = SystemClock.Instance.GetCurrentInstant(); + await db.SaveChangesAsync(); + + return NoContent(); + } + + public class ChatMemberNotifyRequest + { + public ChatMemberNotify? NotifyLevel { get; set; } + public Instant? BreakUntil { get; set; } + } + + [HttpPatch("{roomId:guid}/members/me/notify")] + [Authorize] + public async Task> UpdateChatMemberNotify( + Guid roomId, + [FromBody] ChatMemberNotifyRequest request + ) + { + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); + + var chatRoom = await db.ChatRooms + .Where(r => r.Id == roomId) + .FirstOrDefaultAsync(); + if (chatRoom is null) return NotFound(); + + var accountId = Guid.Parse(currentUser.Id); + var targetMember = await db.ChatMembers + .Where(m => m.AccountId == accountId && m.ChatRoomId == roomId) + .FirstOrDefaultAsync(); + if (targetMember is null) return BadRequest("You have not joined this chat room."); + if (request.NotifyLevel is not null) + targetMember.Notify = request.NotifyLevel.Value; + if (request.BreakUntil is not null) + targetMember.BreakUntil = request.BreakUntil.Value; + + db.ChatMembers.Update(targetMember); + await db.SaveChangesAsync(); + + await crs.PurgeRoomMembersCache(roomId); + + return Ok(targetMember); + } + + [HttpPatch("{roomId:guid}/members/{memberId:guid}/role")] + [Authorize] + public async Task> UpdateChatMemberRole(Guid roomId, Guid memberId, [FromBody] int newRole) + { + if (newRole >= ChatMemberRole.Owner) return BadRequest("Unable to set chat member to owner or greater role."); + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); + + var chatRoom = await db.ChatRooms + .Where(r => r.Id == roomId) + .FirstOrDefaultAsync(); + if (chatRoom is null) return NotFound(); + + // Check if the chat room is owned by a realm + if (chatRoom.RealmId is not null) + { + var realmMember = await db.RealmMembers + .Where(m => m.AccountId == Guid.Parse(currentUser.Id)) + .Where(m => m.RealmId == chatRoom.RealmId) + .FirstOrDefaultAsync(); + if (realmMember is null || realmMember.Role < RealmMemberRole.Moderator) + return StatusCode(403, "You need at least be a realm moderator to change member roles."); + } + else + { + var targetMember = await db.ChatMembers + .Where(m => m.AccountId == memberId && m.ChatRoomId == roomId) + .FirstOrDefaultAsync(); + if (targetMember is null) return NotFound(); + + // Check if the current user has permission to change roles + if ( + !await crs.IsMemberWithRole( + chatRoom.Id, + Guid.Parse(currentUser.Id), + ChatMemberRole.Moderator, + targetMember.Role, + newRole + ) + ) + return StatusCode(403, "You don't have enough permission to edit the roles of members."); + + targetMember.Role = newRole; + db.ChatMembers.Update(targetMember); + await db.SaveChangesAsync(); + + await crs.PurgeRoomMembersCache(roomId); + + _ = als.CreateActionLogAsync(new CreateActionLogRequest + { + Action = "chatrooms.role.edit", + Meta = + { + { "chatroom_id", Google.Protobuf.WellKnownTypes.Value.ForString(roomId.ToString()) }, + { "account_id", Google.Protobuf.WellKnownTypes.Value.ForString(memberId.ToString()) }, + { "new_role", Google.Protobuf.WellKnownTypes.Value.ForNumber(newRole) } + }, + AccountId = currentUser.Id, + UserAgent = Request.Headers.UserAgent, + IpAddress = Request.HttpContext.Connection.RemoteIpAddress?.ToString() + }); + + return Ok(targetMember); + } + + return BadRequest(); + } + + [HttpDelete("{roomId:guid}/members/{memberId:guid}")] + [Authorize] + public async Task RemoveChatMember(Guid roomId, Guid memberId) + { + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); + + var chatRoom = await db.ChatRooms + .Where(r => r.Id == roomId) + .FirstOrDefaultAsync(); + if (chatRoom is null) return NotFound(); + + // Check if the chat room is owned by a realm + if (chatRoom.RealmId is not null) + { + if (!await rs.IsMemberWithRole(chatRoom.RealmId.Value, Guid.Parse(currentUser.Id), + RealmMemberRole.Moderator)) + return StatusCode(403, "You need at least be a realm moderator to remove members."); + } + else + { + if (!await crs.IsMemberWithRole(chatRoom.Id, Guid.Parse(currentUser.Id), ChatMemberRole.Moderator)) + return StatusCode(403, "You need at least be a moderator to remove members."); + + // Find the target member + var member = await db.ChatMembers + .Where(m => m.AccountId == memberId && m.ChatRoomId == roomId) + .FirstOrDefaultAsync(); + if (member is null) return NotFound(); + + // Check if the current user has sufficient permissions + if (!await crs.IsMemberWithRole(chatRoom.Id, memberId, member.Role)) + return StatusCode(403, "You cannot remove members with equal or higher roles."); + + member.LeaveAt = SystemClock.Instance.GetCurrentInstant(); + await db.SaveChangesAsync(); + _ = crs.PurgeRoomMembersCache(roomId); + + _ = als.CreateActionLogAsync(new CreateActionLogRequest + { + Action = "chatrooms.kick", + Meta = + { + { "chatroom_id", Google.Protobuf.WellKnownTypes.Value.ForString(roomId.ToString()) }, + { "account_id", Google.Protobuf.WellKnownTypes.Value.ForString(memberId.ToString()) } + }, + AccountId = currentUser.Id, + UserAgent = Request.Headers.UserAgent, + IpAddress = Request.HttpContext.Connection.RemoteIpAddress?.ToString() + }); + + return NoContent(); + } + + return BadRequest(); + } + + + [HttpPost("{roomId:guid}/members/me")] + [Authorize] + public async Task> JoinChatRoom(Guid roomId) + { + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); + + var chatRoom = await db.ChatRooms + .Where(r => r.Id == roomId) + .FirstOrDefaultAsync(); + if (chatRoom is null) return NotFound(); + if (!chatRoom.IsCommunity) + return StatusCode(403, "This chat room isn't a community. You need an invitation to join."); + + var existingMember = await db.ChatMembers + .FirstOrDefaultAsync(m => m.AccountId == Guid.Parse(currentUser.Id) && m.ChatRoomId == roomId); + if (existingMember != null) + return BadRequest("You are already a member of this chat room."); + + var newMember = new ChatMember + { + AccountId = Guid.Parse(currentUser.Id), + ChatRoomId = roomId, + Role = ChatMemberRole.Member, + JoinedAt = NodaTime.Instant.FromDateTimeUtc(DateTime.UtcNow) + }; + + db.ChatMembers.Add(newMember); + await db.SaveChangesAsync(); + _ = crs.PurgeRoomMembersCache(roomId); + + als.CreateActionLogFromRequest( + ActionLogType.ChatroomJoin, + new Dictionary { { "chatroom_id", roomId } }, Request + ); + + return Ok(chatRoom); + } + + [HttpDelete("{roomId:guid}/members/me")] + [Authorize] + public async Task LeaveChat(Guid roomId) + { + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); + + var member = await db.ChatMembers + .Where(m => m.AccountId == Guid.Parse(currentUser.Id)) + .Where(m => m.ChatRoomId == roomId) + .FirstOrDefaultAsync(); + if (member is null) return NotFound(); + + if (member.Role == ChatMemberRole.Owner) + { + // Check if this is the only owner + var otherOwners = await db.ChatMembers + .Where(m => m.ChatRoomId == roomId) + .Where(m => m.Role == ChatMemberRole.Owner) + .Where(m => m.AccountId != Guid.Parse(currentUser.Id)) + .AnyAsync(); + + if (!otherOwners) + return BadRequest("The last owner cannot leave the chat. Transfer ownership first or delete the chat."); + } + + member.LeaveAt = Instant.FromDateTimeUtc(DateTime.UtcNow); + await db.SaveChangesAsync(); + await crs.PurgeRoomMembersCache(roomId); + + _ = als.CreateActionLogAsync(new CreateActionLogRequest + { + Action = "chatrooms.leave", + Meta = { { "chatroom_id", Google.Protobuf.WellKnownTypes.Value.ForString(roomId.ToString()) } }, + AccountId = currentUser.Id, + UserAgent = Request.Headers.UserAgent, + IpAddress = Request.HttpContext.Connection.RemoteIpAddress?.ToString() + }); + + return NoContent(); + } + + private async Task _SendInviteNotify(ChatMember member, Account sender) + { + string title = localizer["ChatInviteTitle"]; + + string body = member.ChatRoom.Type == ChatRoomType.DirectMessage + ? localizer["ChatInviteDirectBody", sender.Nick] + : localizer["ChatInviteBody", member.ChatRoom.Name ?? "Unnamed"]; + + AccountService.SetCultureInfo(member.Account); + await nty.SendNotification(member.Account, "invites.chats", title, null, body, actionUri: "/chat"); + } +} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Chat/ChatService.cs b/DysonNetwork.Sphere/Chat/ChatService.cs index bdad2d9..af40154 100644 --- a/DysonNetwork.Sphere/Chat/ChatService.cs +++ b/DysonNetwork.Sphere/Chat/ChatService.cs @@ -1,8 +1,7 @@ using System.Text.RegularExpressions; -using DysonNetwork.Sphere.Account; +using DysonNetwork.Shared.Data; +using DysonNetwork.Shared.Proto; using DysonNetwork.Sphere.Chat.Realtime; -using DysonNetwork.Sphere.Connection; -using DysonNetwork.Sphere.Storage; using Microsoft.EntityFrameworkCore; using NodaTime; @@ -10,7 +9,8 @@ namespace DysonNetwork.Sphere.Chat; public partial class ChatService( AppDatabase db, - FileReferenceService fileRefService, + FileService.FileServiceClient filesClient, + FileReferenceService.FileReferenceServiceClient fileRefs, IServiceScopeFactory scopeFactory, IRealtimeService realtime, ILogger logger @@ -33,7 +33,7 @@ public partial class ChatService( // Create a new scope for database operations using var scope = scopeFactory.CreateScope(); var dbContext = scope.ServiceProvider.GetRequiredService(); - var webReader = scope.ServiceProvider.GetRequiredService(); + var webReader = scope.ServiceProvider.GetRequiredService(); var newChat = scope.ServiceProvider.GetRequiredService(); // Preview the links in the message @@ -86,7 +86,7 @@ public partial class ChatService( /// The web reader service /// The message with link previews added to its meta data public async Task PreviewMessageLinkAsync(Message message, - Connection.WebReader.WebReaderService? webReader = null) + WebReader.WebReaderService? webReader = null) { if (string.IsNullOrEmpty(message.Content)) return message; @@ -109,7 +109,7 @@ public partial class ChatService( var embeds = (List>)message.Meta["embeds"]; webReader ??= scopeFactory.CreateScope().ServiceProvider - .GetRequiredService(); + .GetRequiredService(); // Process up to 3 links to avoid excessive processing var processedLinks = 0; @@ -157,16 +157,13 @@ public partial class ChatService( var files = message.Attachments.Distinct().ToList(); if (files.Count != 0) { - var messageResourceId = $"message:{message.Id}"; - foreach (var file in files) + var request = new CreateReferenceBatchRequest { - await fileRefService.CreateReferenceAsync( - file.Id, - ChatFileUsageIdentifier, - messageResourceId, - duration: Duration.FromDays(30) - ); - } + Usage = ChatFileUsageIdentifier, + ResourceId = message.ResourceIdentifier, + }; + request.FilesId.AddRange(message.Attachments.Select(a => a.Id)); + await fileRefs.CreateReferenceBatchAsync(request); } // Then start the delivery process @@ -203,8 +200,7 @@ public partial class ChatService( message.ChatRoom = room; using var scope = scopeFactory.CreateScope(); - var scopedWs = scope.ServiceProvider.GetRequiredService(); - var scopedNty = scope.ServiceProvider.GetRequiredService(); + var scopedNty = scope.ServiceProvider.GetRequiredService(); var scopedCrs = scope.ServiceProvider.GetRequiredService(); var roomSubject = room is { Type: ChatRoomType.DirectMessage, Name: null } ? "DM" : @@ -230,30 +226,32 @@ public partial class ChatService( if (!string.IsNullOrEmpty(room.Name)) metaDict["room_name"] = room.Name; - var notification = new Notification + var notification = new PushNotification { Topic = "messages.new", Title = $"{sender.Nick ?? sender.Account.Nick} ({roomSubject})", - Content = !string.IsNullOrEmpty(message.Content) + Body = !string.IsNullOrEmpty(message.Content) ? message.Content[..Math.Min(message.Content.Length, 100)] : "", - Meta = metaDict, - Priority = 10, }; + notification.Meta.Add(GrpcTypeHelper.ConvertToValueMap(metaDict)); List accountsToNotify = []; foreach (var member in members) { - scopedWs.SendPacketToAccount(member.AccountId, new WebSocketPacket + await scopedNty.PushWebSocketPacketToUsersAsync(new PushWebSocketPacketToUsersRequest { - Type = type, - Data = message + Packet = new WebSocketPacket + { + Type = type, + Data = GrpcTypeHelper.ConvertObjectToValue(metaDict), + }, }); - if (member.Account.Id == sender.AccountId) continue; + if (member.AccountId == sender.AccountId) continue; if (member.Notify == ChatMemberNotify.None) continue; // if (scopedWs.IsUserSubscribedToChatRoom(member.AccountId, room.Id.ToString())) continue; - if (message.MembersMentioned is null || !message.MembersMentioned.Contains(member.Account.Id)) + if (message.MembersMentioned is null || !message.MembersMentioned.Contains(member.AccountId)) { var now = SystemClock.Instance.GetCurrentInstant(); if (member.BreakUntil is not null && member.BreakUntil > now) continue; @@ -265,8 +263,10 @@ public partial class ChatService( logger.LogInformation($"Trying to deliver message to {accountsToNotify.Count} accounts..."); // Only send notifications if there are accounts to notify + var ntyRequest = new SendPushNotificationToUsersRequest { Notification = notification }; + ntyRequest.UserIds.AddRange(accountsToNotify.Select(a => a.Id.ToString())); if (accountsToNotify.Count > 0) - await scopedNty.SendNotificationBatch(notification, accountsToNotify, save: false); + await scopedNty.SendPushNotificationToUsersAsync(ntyRequest); logger.LogInformation($"Delivered message to {accountsToNotify.Count} accounts."); } @@ -495,25 +495,24 @@ public partial class ChatService( var messageResourceId = $"message:{message.Id}"; // Delete existing references for this message - await fileRefService.DeleteResourceReferencesAsync(messageResourceId); + await fileRefs.DeleteResourceReferencesAsync( + new DeleteResourceReferencesRequest { ResourceId = messageResourceId } + ); // Create new references for each attachment - foreach (var fileId in attachmentsId) + var createRequest = new CreateReferenceBatchRequest { - await fileRefService.CreateReferenceAsync( - fileId, - ChatFileUsageIdentifier, - messageResourceId, - duration: Duration.FromDays(30) - ); - } + Usage = ChatFileUsageIdentifier, + ResourceId = messageResourceId, + }; + createRequest.FilesId.AddRange(attachmentsId); + await fileRefs.CreateReferenceBatchAsync(createRequest); - // Update message attachments by getting files from database - var files = await db.Files - .Where(f => attachmentsId.Contains(f.Id)) - .ToListAsync(); - - message.Attachments = files.Select(x => x.ToReferenceObject()).ToList(); + // Update message attachments by getting files from da + var queryRequest = new GetFileBatchRequest(); + queryRequest.Ids.AddRange(attachmentsId); + var queryResult = await filesClient.GetFileBatchAsync(queryRequest); + message.Attachments = queryResult.Files.Select(CloudFileReferenceObject.FromProtoValue).ToList(); } message.EditedAt = SystemClock.Instance.GetCurrentInstant(); @@ -542,7 +541,9 @@ public partial class ChatService( { // Remove all file references for this message var messageResourceId = $"message:{message.Id}"; - await fileRefService.DeleteResourceReferencesAsync(messageResourceId); + await fileRefs.DeleteResourceReferencesAsync( + new DeleteResourceReferencesRequest { ResourceId = messageResourceId } + ); db.ChatMessages.Remove(message); await db.SaveChangesAsync(); @@ -575,4 +576,4 @@ public class SyncResponse { public List Changes { get; set; } = []; public Instant CurrentTimestamp { get; set; } -} +} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Chat/Realtime/IRealtimeService.cs b/DysonNetwork.Sphere/Chat/Realtime/IRealtimeService.cs index cd34f8b..1d08b18 100644 --- a/DysonNetwork.Sphere/Chat/Realtime/IRealtimeService.cs +++ b/DysonNetwork.Sphere/Chat/Realtime/IRealtimeService.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; +using DysonNetwork.Shared.Proto; namespace DysonNetwork.Sphere.Chat.Realtime; diff --git a/DysonNetwork.Sphere/Chat/Realtime/LivekitService.cs b/DysonNetwork.Sphere/Chat/Realtime/LivekitService.cs index 4313910..f3dfe23 100644 --- a/DysonNetwork.Sphere/Chat/Realtime/LivekitService.cs +++ b/DysonNetwork.Sphere/Chat/Realtime/LivekitService.cs @@ -1,32 +1,31 @@ -using DysonNetwork.Sphere.Connection; -using DysonNetwork.Sphere.Storage; using Livekit.Server.Sdk.Dotnet; using Microsoft.EntityFrameworkCore; using NodaTime; using System.Text.Json; +using DysonNetwork.Shared.Cache; +using DysonNetwork.Shared.Data; +using DysonNetwork.Shared.Proto; namespace DysonNetwork.Sphere.Chat.Realtime; /// /// LiveKit implementation of the real-time communication service /// -public class LivekitRealtimeService : IRealtimeService +public class LiveKitRealtimeService : IRealtimeService { private readonly AppDatabase _db; private readonly ICacheService _cache; - private readonly WebSocketService _ws; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly RoomServiceClient _roomService; private readonly AccessToken _accessToken; private readonly WebhookReceiver _webhookReceiver; - public LivekitRealtimeService( + public LiveKitRealtimeService( IConfiguration configuration, - ILogger logger, + ILogger logger, AppDatabase db, - ICacheService cache, - WebSocketService ws + ICacheService cache ) { _logger = logger; @@ -45,7 +44,6 @@ public class LivekitRealtimeService : IRealtimeService _db = db; _cache = cache; - _ws = ws; } /// @@ -159,7 +157,7 @@ public class LivekitRealtimeService : IRealtimeService evt.Room.Name, evt.Participant.Identity); // Broadcast participant list update to all participants - await _BroadcastParticipantUpdate(evt.Room.Name); + // await _BroadcastParticipantUpdate(evt.Room.Name); } break; @@ -174,7 +172,7 @@ public class LivekitRealtimeService : IRealtimeService evt.Room.Name, evt.Participant.Identity); // Broadcast participant list update to all participants - await _BroadcastParticipantUpdate(evt.Room.Name); + // await _BroadcastParticipantUpdate(evt.Room.Name); } break; @@ -310,82 +308,4 @@ public class LivekitRealtimeService : IRealtimeService JoinedAt = DateTime.UtcNow }; } - - // Broadcast participant update to all participants in a room - private async Task _BroadcastParticipantUpdate(string roomName) - { - try - { - // Get the room ID from the session name - var roomInfo = await _db.ChatRealtimeCall - .Where(c => c.SessionId == roomName && c.EndedAt == null) - .Select(c => new { c.RoomId, c.Id }) - .FirstOrDefaultAsync(); - - if (roomInfo == null) - { - _logger.LogWarning("Could not find room info for session: {SessionName}", roomName); - return; - } - - // Get current participants - var livekitParticipants = await GetRoomParticipantsAsync(roomName); - - // Get all room members who should receive this update - var roomMembers = await _db.ChatMembers - .Where(m => m.ChatRoomId == roomInfo.RoomId && m.LeaveAt == null) - .Select(m => m.AccountId) - .ToListAsync(); - - // Get member profiles for participants who have account IDs - var accountIds = livekitParticipants - .Where(p => p.AccountId.HasValue) - .Select(p => p.AccountId!.Value) - .ToList(); - - var memberProfiles = new Dictionary(); - if (accountIds.Count != 0) - { - memberProfiles = await _db.ChatMembers - .Where(m => m.ChatRoomId == roomInfo.RoomId && accountIds.Contains(m.AccountId)) - .Include(m => m.Account) - .ThenInclude(m => m.Profile) - .ToDictionaryAsync(m => m.AccountId, m => m); - } - - // Convert to CallParticipant objects - var participants = livekitParticipants.Select(p => new CallParticipant - { - Identity = p.Identity, - Name = p.Name, - AccountId = p.AccountId, - JoinedAt = p.JoinedAt, - Profile = p.AccountId.HasValue && memberProfiles.TryGetValue(p.AccountId.Value, out var profile) - ? profile - : null - }).ToList(); - - // Create the update packet with CallParticipant objects - var updatePacket = new WebSocketPacket - { - Type = WebSocketPacketType.CallParticipantsUpdate, - Data = new Dictionary - { - { "room_id", roomInfo.RoomId }, - { "call_id", roomInfo.Id }, - { "participants", participants } - } - }; - - // Send the update to all members - foreach (var accountId in roomMembers) - { - _ws.SendPacketToAccount(accountId, updatePacket); - } - } - catch (Exception ex) - { - _logger.LogError(ex, "Error broadcasting participant update for room {RoomName}", roomName); - } - } } \ No newline at end of file diff --git a/DysonNetwork.Sphere/Chat/RealtimeCallController.cs b/DysonNetwork.Sphere/Chat/RealtimeCallController.cs index 6d300a2..998b9a5 100644 --- a/DysonNetwork.Sphere/Chat/RealtimeCallController.cs +++ b/DysonNetwork.Sphere/Chat/RealtimeCallController.cs @@ -1,3 +1,4 @@ +using DysonNetwork.Shared.Proto; using DysonNetwork.Sphere.Chat.Realtime; using Livekit.Server.Sdk.Dotnet; using Microsoft.AspNetCore.Authorization; @@ -48,8 +49,9 @@ public class RealtimeCallController( { if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); + var accountId = Guid.Parse(currentUser.Id); var member = await db.ChatMembers - .Where(m => m.AccountId == currentUser.Id && m.ChatRoomId == roomId) + .Where(m => m.AccountId == accountId && m.ChatRoomId == roomId) .FirstOrDefaultAsync(); if (member == null || member.Role < ChatMemberRole.Member) @@ -74,8 +76,9 @@ public class RealtimeCallController( if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); // Check if the user is a member of the chat room + var accountId = Guid.Parse(currentUser.Id); var member = await db.ChatMembers - .Where(m => m.AccountId == currentUser.Id && m.ChatRoomId == roomId) + .Where(m => m.AccountId == accountId && m.ChatRoomId == roomId) .FirstOrDefaultAsync(); if (member == null || member.Role < ChatMemberRole.Member) @@ -102,7 +105,7 @@ public class RealtimeCallController( // Get current participants from the LiveKit service var participants = new List(); - if (realtime is LivekitRealtimeService livekitService) + if (realtime is LiveKitRealtimeService livekitService) { var roomParticipants = await livekitService.GetRoomParticipantsAsync(ongoingCall.SessionId); participants = []; @@ -146,8 +149,9 @@ public class RealtimeCallController( { if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); + var accountId = Guid.Parse(currentUser.Id); var member = await db.ChatMembers - .Where(m => m.AccountId == currentUser.Id && m.ChatRoomId == roomId) + .Where(m => m.AccountId == accountId && m.ChatRoomId == roomId) .Include(m => m.ChatRoom) .FirstOrDefaultAsync(); if (member == null || member.Role < ChatMemberRole.Member) @@ -165,8 +169,9 @@ public class RealtimeCallController( { if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); + var accountId = Guid.Parse(currentUser.Id); var member = await db.ChatMembers - .Where(m => m.AccountId == currentUser.Id && m.ChatRoomId == roomId) + .Where(m => m.AccountId == accountId && m.ChatRoomId == roomId) .FirstOrDefaultAsync(); if (member == null || member.Role < ChatMemberRole.Member) return StatusCode(403, "You need to be a normal member to end a call."); diff --git a/DysonNetwork.Sphere/Developer/CustomApp.cs b/DysonNetwork.Sphere/Developer/CustomApp.cs index 0d3ba5d..425e646 100644 --- a/DysonNetwork.Sphere/Developer/CustomApp.cs +++ b/DysonNetwork.Sphere/Developer/CustomApp.cs @@ -1,8 +1,7 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using System.Text.Json.Serialization; -using DysonNetwork.Sphere.Account; -using DysonNetwork.Sphere.Storage; +using DysonNetwork.Shared.Data; using NodaTime; namespace DysonNetwork.Sphere.Developer; diff --git a/DysonNetwork.Sphere/Developer/CustomAppController.cs b/DysonNetwork.Sphere/Developer/CustomAppController.cs index c0e42ec..e9d5eb0 100644 --- a/DysonNetwork.Sphere/Developer/CustomAppController.cs +++ b/DysonNetwork.Sphere/Developer/CustomAppController.cs @@ -1,5 +1,5 @@ using System.ComponentModel.DataAnnotations; -using DysonNetwork.Sphere.Account; +using DysonNetwork.Shared.Proto; using DysonNetwork.Sphere.Publisher; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; @@ -55,7 +55,7 @@ public class CustomAppController(CustomAppService customApps, PublisherService p var publisher = await ps.GetPublisherByName(pubName); if (publisher is null) return NotFound(); - if (!await ps.IsMemberWithRole(publisher.Id, currentUser.Id, PublisherMemberRole.Editor)) + if (!await ps.IsMemberWithRole(publisher.Id, Guid.Parse(currentUser.Id), PublisherMemberRole.Editor)) return StatusCode(403, "You must be an editor of the publisher to create a custom app"); if (!await ps.HasFeature(publisher.Id, PublisherFeatureFlag.Develop)) return StatusCode(403, "Publisher must be a developer to create a custom app"); @@ -84,7 +84,7 @@ public class CustomAppController(CustomAppService customApps, PublisherService p var publisher = await ps.GetPublisherByName(pubName); if (publisher is null) return NotFound(); - if (!await ps.IsMemberWithRole(publisher.Id, currentUser.Id, PublisherMemberRole.Editor)) + if (!await ps.IsMemberWithRole(publisher.Id, Guid.Parse(currentUser.Id), PublisherMemberRole.Editor)) return StatusCode(403, "You must be an editor of the publisher to update a custom app"); var app = await customApps.GetAppAsync(id, publisherId: publisher.Id); @@ -114,7 +114,7 @@ public class CustomAppController(CustomAppService customApps, PublisherService p var publisher = await ps.GetPublisherByName(pubName); if (publisher is null) return NotFound(); - if (!await ps.IsMemberWithRole(publisher.Id, currentUser.Id, PublisherMemberRole.Editor)) + if (!await ps.IsMemberWithRole(publisher.Id, Guid.Parse(currentUser.Id), PublisherMemberRole.Editor)) return StatusCode(403, "You must be an editor of the publisher to delete a custom app"); var app = await customApps.GetAppAsync(id, publisherId: publisher.Id); diff --git a/DysonNetwork.Sphere/Developer/CustomAppService.cs b/DysonNetwork.Sphere/Developer/CustomAppService.cs index 6455b37..d2fc0a3 100644 --- a/DysonNetwork.Sphere/Developer/CustomAppService.cs +++ b/DysonNetwork.Sphere/Developer/CustomAppService.cs @@ -1,10 +1,14 @@ -using DysonNetwork.Sphere.Publisher; -using DysonNetwork.Sphere.Storage; +using DysonNetwork.Shared.Data; +using DysonNetwork.Shared.Proto; using Microsoft.EntityFrameworkCore; namespace DysonNetwork.Sphere.Developer; -public class CustomAppService(AppDatabase db, FileReferenceService fileRefService) +public class CustomAppService( + AppDatabase db, + FileReferenceService.FileReferenceServiceClient fileRefs, + FileService.FileServiceClient files +) { public async Task CreateAppAsync( Publisher.Publisher pub, @@ -21,36 +25,46 @@ public class CustomAppService(AppDatabase db, FileReferenceService fileRefServic OauthConfig = request.OauthConfig, PublisherId = pub.Id }; - + if (request.PictureId is not null) { - var picture = await db.Files.Where(f => f.Id == request.PictureId).FirstOrDefaultAsync(); + var picture = await files.GetFileAsync( + new GetFileRequest + { + Id = request.PictureId + } + ); if (picture is null) throw new InvalidOperationException("Invalid picture id, unable to find the file on cloud."); - - app.Picture = picture.ToReferenceObject(); + app.Picture = CloudFileReferenceObject.FromProtoValue(picture); // Create a new reference - await fileRefService.CreateReferenceAsync( - picture.Id, - "custom-apps.picture", - app.ResourceIdentifier + await fileRefs.CreateReferenceAsync( + new CreateReferenceRequest + { + FileId = picture.Id, + Usage = "custom-apps.picture", + ResourceId = app.ResourceIdentifier + } ); } - if (request.BackgroundId is not null) { - var background = await db.Files.Where(f => f.Id == request.BackgroundId).FirstOrDefaultAsync(); + var background = await files.GetFileAsync( + new GetFileRequest { Id = request.BackgroundId } + ); if (background is null) throw new InvalidOperationException("Invalid picture id, unable to find the file on cloud."); - - app.Background = background.ToReferenceObject(); + app.Background = CloudFileReferenceObject.FromProtoValue(background); // Create a new reference - await fileRefService.CreateReferenceAsync( - background.Id, - "custom-apps.background", - app.ResourceIdentifier + await fileRefs.CreateReferenceAsync( + new CreateReferenceRequest + { + FileId = background.Id, + Usage = "custom-apps.background", + ResourceId = app.ResourceIdentifier + } ); } @@ -90,39 +104,43 @@ public class CustomAppService(AppDatabase db, FileReferenceService fileRefServic if (request.PictureId is not null) { - var picture = await db.Files.Where(f => f.Id == request.PictureId).FirstOrDefaultAsync(); + var picture = await files.GetFileAsync( + new GetFileRequest + { + Id = request.PictureId + } + ); if (picture is null) throw new InvalidOperationException("Invalid picture id, unable to find the file on cloud."); - - if (app.Picture is not null) - await fileRefService.DeleteResourceReferencesAsync(app.ResourceIdentifier, "custom-apps.picture"); - - app.Picture = picture.ToReferenceObject(); + app.Picture = CloudFileReferenceObject.FromProtoValue(picture); // Create a new reference - await fileRefService.CreateReferenceAsync( - picture.Id, - "custom-apps.picture", - app.ResourceIdentifier + await fileRefs.CreateReferenceAsync( + new CreateReferenceRequest + { + FileId = picture.Id, + Usage = "custom-apps.picture", + ResourceId = app.ResourceIdentifier + } ); } - if (request.BackgroundId is not null) { - var background = await db.Files.Where(f => f.Id == request.BackgroundId).FirstOrDefaultAsync(); + var background = await files.GetFileAsync( + new GetFileRequest { Id = request.BackgroundId } + ); if (background is null) throw new InvalidOperationException("Invalid picture id, unable to find the file on cloud."); - - if (app.Background is not null) - await fileRefService.DeleteResourceReferencesAsync(app.ResourceIdentifier, "custom-apps.background"); - - app.Background = background.ToReferenceObject(); + app.Background = CloudFileReferenceObject.FromProtoValue(background); // Create a new reference - await fileRefService.CreateReferenceAsync( - background.Id, - "custom-apps.background", - app.ResourceIdentifier + await fileRefs.CreateReferenceAsync( + new CreateReferenceRequest + { + FileId = background.Id, + Usage = "custom-apps.background", + ResourceId = app.ResourceIdentifier + } ); } @@ -142,8 +160,12 @@ public class CustomAppService(AppDatabase db, FileReferenceService fileRefServic db.CustomApps.Remove(app); await db.SaveChangesAsync(); - - await fileRefService.DeleteResourceReferencesAsync(app.ResourceIdentifier); + + await fileRefs.DeleteResourceReferencesAsync(new DeleteResourceReferencesRequest + { + ResourceId = app.ResourceIdentifier + } + ); return true; } diff --git a/DysonNetwork.Sphere/Developer/DeveloperController.cs b/DysonNetwork.Sphere/Developer/DeveloperController.cs index 4981bf6..2f251ea 100644 --- a/DysonNetwork.Sphere/Developer/DeveloperController.cs +++ b/DysonNetwork.Sphere/Developer/DeveloperController.cs @@ -1,4 +1,4 @@ -using DysonNetwork.Sphere.Account; +using DysonNetwork.Shared.Proto; using DysonNetwork.Sphere.Permission; using DysonNetwork.Sphere.Publisher; using Microsoft.AspNetCore.Authorization; @@ -13,7 +13,7 @@ namespace DysonNetwork.Sphere.Developer; public class DeveloperController( AppDatabase db, PublisherService ps, - ActionLogService als + ActionLogService.ActionLogServiceClient als ) : ControllerBase { @@ -64,10 +64,10 @@ public class DeveloperController( public async Task>> ListJoinedDevelopers() { if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); - var userId = currentUser.Id; + var accountId = Guid.Parse(currentUser.Id); var members = await db.PublisherMembers - .Where(m => m.AccountId == userId) + .Where(m => m.AccountId == accountId) .Where(m => m.JoinedAt != null) .Include(e => e.Publisher) .ToListAsync(); @@ -94,7 +94,7 @@ public class DeveloperController( public async Task> EnrollDeveloperProgram(string name) { if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); - var userId = currentUser.Id; + var accountId = Guid.Parse(currentUser.Id); var publisher = await db.Publishers .Where(p => p.Name == name) @@ -105,7 +105,7 @@ public class DeveloperController( var isOwner = await db.PublisherMembers .AnyAsync(m => m.PublisherId == publisher.Id && - m.AccountId == userId && + m.AccountId == accountId && m.Role == PublisherMemberRole.Owner && m.JoinedAt != null); @@ -132,6 +132,19 @@ public class DeveloperController( db.PublisherFeatures.Add(feature); await db.SaveChangesAsync(); + _ = als.CreateActionLogAsync(new CreateActionLogRequest + { + Action = "developers.enroll", + Meta = + { + { "publisher_id", Google.Protobuf.WellKnownTypes.Value.ForString(publisher.Id.ToString()) }, + { "publisher_name", Google.Protobuf.WellKnownTypes.Value.ForString(publisher.Name) } + }, + AccountId = currentUser.Id, + UserAgent = Request.Headers.UserAgent, + IpAddress = Request.HttpContext.Connection.RemoteIpAddress?.ToString() + }); + return Ok(publisher); } diff --git a/DysonNetwork.Sphere/Pages/Posts/PostDetail.cshtml.cs b/DysonNetwork.Sphere/Pages/Posts/PostDetail.cshtml.cs index 27a43d8..b14a396 100644 --- a/DysonNetwork.Sphere/Pages/Posts/PostDetail.cshtml.cs +++ b/DysonNetwork.Sphere/Pages/Posts/PostDetail.cshtml.cs @@ -28,7 +28,7 @@ public class PostDetailModel( var userFriends = currentUser is null ? [] : (await accounts.ListFriendsAsync( - new ListUserRelationshipSimpleRequest { AccountId = currentUser.Id } + new ListRelationshipSimpleRequest { AccountId = currentUser.Id } )).AccountsId.Select(Guid.Parse).ToList(); var userPublishers = currentUser is null ? [] : await pub.GetUserPublishers(accountId); diff --git a/DysonNetwork.Sphere/Pages/Shared/_Layout.cshtml b/DysonNetwork.Sphere/Pages/Shared/_Layout.cshtml index d229ef9..4c20d58 100644 --- a/DysonNetwork.Sphere/Pages/Shared/_Layout.cshtml +++ b/DysonNetwork.Sphere/Pages/Shared/_Layout.cshtml @@ -1,4 +1,3 @@ -@using DysonNetwork.Sphere.Auth @@ -24,31 +23,6 @@ Solar Network
-
diff --git a/DysonNetwork.Sphere/Permission/Permission.cs b/DysonNetwork.Sphere/Permission/Permission.cs deleted file mode 100644 index fe68844..0000000 --- a/DysonNetwork.Sphere/Permission/Permission.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; -using System.Text.Json; -using System.Text.Json.Serialization; -using Microsoft.EntityFrameworkCore; -using NodaTime; - -namespace DysonNetwork.Sphere.Permission; - -/// The permission node model provides the infrastructure of permission control in Dyson Network. -/// It based on the ABAC permission model. -/// -/// The value can be any type, boolean and number for most cases and stored in jsonb. -/// -/// The area represents the region this permission affects. For example, the pub:<publisherId> -/// indicates it's a permission node for the publishers managing. -/// -/// And the actor shows who owns the permission, in most cases, the user:<userId> -/// and when the permission node has a GroupId, the actor will be set to the group, but it won't work on checking -/// expect the member of that permission group inherent the permission from the group. -[Index(nameof(Key), nameof(Area), nameof(Actor))] -public class PermissionNode : ModelBase, IDisposable -{ - public Guid Id { get; set; } = Guid.NewGuid(); - [MaxLength(1024)] public string Actor { get; set; } = null!; - [MaxLength(1024)] public string Area { get; set; } = null!; - [MaxLength(1024)] public string Key { get; set; } = null!; - [Column(TypeName = "jsonb")] public JsonDocument Value { get; set; } = null!; - public Instant? ExpiredAt { get; set; } = null; - public Instant? AffectedAt { get; set; } = null; - - public Guid? GroupId { get; set; } = null; - [JsonIgnore] public PermissionGroup? Group { get; set; } = null; - - public void Dispose() - { - Value.Dispose(); - GC.SuppressFinalize(this); - } -} - -public class PermissionGroup : ModelBase -{ - public Guid Id { get; set; } = Guid.NewGuid(); - [MaxLength(1024)] public string Key { get; set; } = null!; - - public ICollection Nodes { get; set; } = new List(); - [JsonIgnore] public ICollection Members { get; set; } = new List(); -} - -public class PermissionGroupMember : ModelBase -{ - public Guid GroupId { get; set; } - public PermissionGroup Group { get; set; } = null!; - [MaxLength(1024)] public string Actor { get; set; } = null!; - - public Instant? ExpiredAt { get; set; } - public Instant? AffectedAt { get; set; } -} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Permission/PermissionMiddleware.cs b/DysonNetwork.Sphere/Permission/PermissionMiddleware.cs deleted file mode 100644 index b67ba20..0000000 --- a/DysonNetwork.Sphere/Permission/PermissionMiddleware.cs +++ /dev/null @@ -1,51 +0,0 @@ -namespace DysonNetwork.Sphere.Permission; - -using System; - -[AttributeUsage(AttributeTargets.Method, Inherited = true)] -public class RequiredPermissionAttribute(string area, string key) : Attribute -{ - public string Area { get; set; } = area; - public string Key { get; } = key; -} - -public class PermissionMiddleware(RequestDelegate next) -{ - public async Task InvokeAsync(HttpContext httpContext, PermissionService pm) - { - var endpoint = httpContext.GetEndpoint(); - - var attr = endpoint?.Metadata - .OfType() - .FirstOrDefault(); - - if (attr != null) - { - if (httpContext.Items["CurrentUser"] is not Account currentUser) - { - httpContext.Response.StatusCode = StatusCodes.Status403Forbidden; - await httpContext.Response.WriteAsync("Unauthorized"); - return; - } - - if (currentUser.IsSuperuser) - { - // Bypass the permission check for performance - await next(httpContext); - return; - } - - var actor = $"user:{currentUser.Id}"; - var permNode = await pm.GetPermissionAsync(actor, attr.Area, attr.Key); - - if (!permNode) - { - httpContext.Response.StatusCode = StatusCodes.Status403Forbidden; - await httpContext.Response.WriteAsync($"Permission {attr.Area}/{attr.Key} = {true} was required."); - return; - } - } - - await next(httpContext); - } -} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Permission/PermissionService.cs b/DysonNetwork.Sphere/Permission/PermissionService.cs deleted file mode 100644 index 1c7a865..0000000 --- a/DysonNetwork.Sphere/Permission/PermissionService.cs +++ /dev/null @@ -1,197 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using NodaTime; -using System.Text.Json; -using DysonNetwork.Sphere.Storage; - -namespace DysonNetwork.Sphere.Permission; - -public class PermissionService( - AppDatabase db, - ICacheService cache -) -{ - private static readonly TimeSpan CacheExpiration = TimeSpan.FromMinutes(1); - - private const string PermCacheKeyPrefix = "perm:"; - private const string PermGroupCacheKeyPrefix = "perm-cg:"; - private const string PermissionGroupPrefix = "perm-g:"; - - private static string _GetPermissionCacheKey(string actor, string area, string key) => - PermCacheKeyPrefix + actor + ":" + area + ":" + key; - - private static string _GetGroupsCacheKey(string actor) => - PermGroupCacheKeyPrefix + actor; - - private static string _GetPermissionGroupKey(string actor) => - PermissionGroupPrefix + actor; - - public async Task HasPermissionAsync(string actor, string area, string key) - { - var value = await GetPermissionAsync(actor, area, key); - return value; - } - - public async Task GetPermissionAsync(string actor, string area, string key) - { - var cacheKey = _GetPermissionCacheKey(actor, area, key); - - var (hit, cachedValue) = await cache.GetAsyncWithStatus(cacheKey); - if (hit) - return cachedValue; - - var now = SystemClock.Instance.GetCurrentInstant(); - var groupsKey = _GetGroupsCacheKey(actor); - - var groupsId = await cache.GetAsync>(groupsKey); - if (groupsId == null) - { - groupsId = await db.PermissionGroupMembers - .Where(n => n.Actor == actor) - .Where(n => n.ExpiredAt == null || n.ExpiredAt > now) - .Where(n => n.AffectedAt == null || n.AffectedAt <= now) - .Select(e => e.GroupId) - .ToListAsync(); - - await cache.SetWithGroupsAsync(groupsKey, groupsId, - [_GetPermissionGroupKey(actor)], - CacheExpiration); - } - - var permission = await db.PermissionNodes - .Where(n => (n.GroupId == null && n.Actor == actor) || - (n.GroupId != null && groupsId.Contains(n.GroupId.Value))) - .Where(n => n.Key == key && n.Area == area) - .Where(n => n.ExpiredAt == null || n.ExpiredAt > now) - .Where(n => n.AffectedAt == null || n.AffectedAt <= now) - .FirstOrDefaultAsync(); - - var result = permission is not null ? _DeserializePermissionValue(permission.Value) : default; - - await cache.SetWithGroupsAsync(cacheKey, result, - [_GetPermissionGroupKey(actor)], - CacheExpiration); - - return result; - } - - public async Task AddPermissionNode( - string actor, - string area, - string key, - T value, - Instant? expiredAt = null, - Instant? affectedAt = null - ) - { - if (value is null) throw new ArgumentNullException(nameof(value)); - - var node = new PermissionNode - { - Actor = actor, - Key = key, - Area = area, - Value = _SerializePermissionValue(value), - ExpiredAt = expiredAt, - AffectedAt = affectedAt - }; - - db.PermissionNodes.Add(node); - await db.SaveChangesAsync(); - - // Invalidate related caches - await InvalidatePermissionCacheAsync(actor, area, key); - - return node; - } - - public async Task AddPermissionNodeToGroup( - PermissionGroup group, - string actor, - string area, - string key, - T value, - Instant? expiredAt = null, - Instant? affectedAt = null - ) - { - if (value is null) throw new ArgumentNullException(nameof(value)); - - var node = new PermissionNode - { - Actor = actor, - Key = key, - Area = area, - Value = _SerializePermissionValue(value), - ExpiredAt = expiredAt, - AffectedAt = affectedAt, - Group = group, - GroupId = group.Id - }; - - db.PermissionNodes.Add(node); - await db.SaveChangesAsync(); - - // Invalidate related caches - await InvalidatePermissionCacheAsync(actor, area, key); - await cache.RemoveAsync(_GetGroupsCacheKey(actor)); - await cache.RemoveGroupAsync(_GetPermissionGroupKey(actor)); - - return node; - } - - public async Task RemovePermissionNode(string actor, string area, string key) - { - var node = await db.PermissionNodes - .Where(n => n.Actor == actor && n.Area == area && n.Key == key) - .FirstOrDefaultAsync(); - if (node is not null) db.PermissionNodes.Remove(node); - await db.SaveChangesAsync(); - - // Invalidate cache - await InvalidatePermissionCacheAsync(actor, area, key); - } - - public async Task RemovePermissionNodeFromGroup(PermissionGroup group, string actor, string area, string key) - { - var node = await db.PermissionNodes - .Where(n => n.GroupId == group.Id) - .Where(n => n.Actor == actor && n.Area == area && n.Key == key) - .FirstOrDefaultAsync(); - if (node is null) return; - db.PermissionNodes.Remove(node); - await db.SaveChangesAsync(); - - // Invalidate caches - await InvalidatePermissionCacheAsync(actor, area, key); - await cache.RemoveAsync(_GetGroupsCacheKey(actor)); - await cache.RemoveGroupAsync(_GetPermissionGroupKey(actor)); - } - - private async Task InvalidatePermissionCacheAsync(string actor, string area, string key) - { - var cacheKey = _GetPermissionCacheKey(actor, area, key); - await cache.RemoveAsync(cacheKey); - } - - private static T? _DeserializePermissionValue(JsonDocument json) - { - return JsonSerializer.Deserialize(json.RootElement.GetRawText()); - } - - private static JsonDocument _SerializePermissionValue(T obj) - { - var str = JsonSerializer.Serialize(obj); - return JsonDocument.Parse(str); - } - - public static PermissionNode NewPermissionNode(string actor, string area, string key, T value) - { - return new PermissionNode - { - Actor = actor, - Area = area, - Key = key, - Value = _SerializePermissionValue(value), - }; - } -} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Post/PostController.cs b/DysonNetwork.Sphere/Post/PostController.cs index 3def48a..3af0f0c 100644 --- a/DysonNetwork.Sphere/Post/PostController.cs +++ b/DysonNetwork.Sphere/Post/PostController.cs @@ -1,4 +1,6 @@ using System.ComponentModel.DataAnnotations; +using DysonNetwork.Shared.Content; +using DysonNetwork.Shared.Data; using DysonNetwork.Shared.Proto; using DysonNetwork.Sphere.Permission; using DysonNetwork.Sphere.Publisher; @@ -29,8 +31,16 @@ public class PostController( { HttpContext.Items.TryGetValue("CurrentUser", out var currentUserValue); var currentUser = currentUserValue as Account; - var userFriends = currentUser is null ? [] : await rels.ListAccountFriends(currentUser); - var userPublishers = currentUser is null ? [] : await pub.GetUserPublishers(currentUser.Id); + + List userFriends = []; + if (currentUser != null) + { + var friendsResponse = await accounts.ListFriendsAsync(new ListRelationshipSimpleRequest + { AccountId = currentUser.Id }); + userFriends = friendsResponse.AccountsId.Select(Guid.Parse).ToList(); + } + + var userPublishers = currentUser is null ? [] : await pub.GetUserPublishers(Guid.Parse(currentUser.Id)); var publisher = pubName == null ? null : await db.Publishers.FirstOrDefaultAsync(p => p.Name == pubName); @@ -64,11 +74,18 @@ public class PostController( { if (HttpContext.Items["IsWebPage"] as bool? ?? true) return RedirectToPage("/Posts/PostDetail", new { PostId = id }); - + HttpContext.Items.TryGetValue("CurrentUser", out var currentUserValue); var currentUser = currentUserValue as Account; - var userFriends = currentUser is null ? [] : await rels.ListAccountFriends(currentUser); - var userPublishers = currentUser is null ? [] : await pub.GetUserPublishers(currentUser.Id); + List userFriends = []; + if (currentUser != null) + { + var friendsResponse = await accounts.ListFriendsAsync(new ListRelationshipSimpleRequest + { AccountId = currentUser.Id }); + userFriends = friendsResponse.AccountsId.Select(Guid.Parse).ToList(); + } + + var userPublishers = currentUser is null ? [] : await pub.GetUserPublishers(Guid.Parse(currentUser.Id)); var post = await db.Posts .Where(e => e.Id == id) @@ -99,8 +116,14 @@ public class PostController( HttpContext.Items.TryGetValue("CurrentUser", out var currentUserValue); var currentUser = currentUserValue as Account; - var userFriends = currentUser is null ? [] : await rels.ListAccountFriends(currentUser); - var userPublishers = currentUser is null ? [] : await pub.GetUserPublishers(currentUser.Id); + List userFriends = []; + if (currentUser != null) + { + var friendsResponse = await accounts.ListFriendsAsync(new ListRelationshipSimpleRequest + { AccountId = currentUser.Id }); + userFriends = friendsResponse.AccountsId.Select(Guid.Parse).ToList(); + } + var userPublishers = currentUser is null ? [] : await pub.GetUserPublishers(Guid.Parse(currentUser.Id)); var queryable = db.Posts .FilterWithVisibility(currentUser, userFriends, userPublishers, isListing: true) @@ -136,8 +159,16 @@ public class PostController( { HttpContext.Items.TryGetValue("CurrentUser", out var currentUserValue); var currentUser = currentUserValue as Account; - var userFriends = currentUser is null ? [] : await rels.ListAccountFriends(currentUser); - var userPublishers = currentUser is null ? [] : await pub.GetUserPublishers(currentUser.Id); + + List userFriends = []; + if (currentUser != null) + { + var friendsResponse = await accounts.ListFriendsAsync(new ListRelationshipSimpleRequest + { AccountId = currentUser.Id }); + userFriends = friendsResponse.AccountsId.Select(Guid.Parse).ToList(); + } + + var userPublishers = currentUser is null ? [] : await pub.GetUserPublishers(Guid.Parse(currentUser.Id)); var parent = await db.Posts .Where(e => e.Id == id) @@ -199,12 +230,14 @@ public class PostController( return BadRequest("Content is required."); if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); + var accountId = Guid.Parse(currentUser.Id); + Publisher.Publisher? publisher; if (publisherName is null) { // Use the first personal publisher publisher = await db.Publishers.FirstOrDefaultAsync(e => - e.AccountId == currentUser.Id && e.Type == PublisherType.Individual); + e.AccountId == accountId && e.Type == PublisherType.Individual); } else { @@ -212,7 +245,7 @@ public class PostController( if (publisher is null) return BadRequest("Publisher was not found."); var member = await db.PublisherMembers.FirstOrDefaultAsync(e => - e.AccountId == currentUser.Id && e.PublisherId == publisher.Id); + e.AccountId == accountId && e.PublisherId == publisher.Id); if (member is null) return StatusCode(403, "You even wasn't a member of the publisher you specified."); if (member.Role < PublisherMemberRole.Editor) return StatusCode(403, "You need at least be an editor to post as this publisher."); @@ -263,10 +296,14 @@ public class PostController( return BadRequest(err.Message); } - als.CreateActionLogFromRequest( - ActionLogType.PostCreate, - new Dictionary { { "post_id", post.Id } }, Request - ); + _ = als.CreateActionLogAsync(new CreateActionLogRequest + { + Action = ActionLogType.PostCreate, + Meta = { { "post_id", Google.Protobuf.WellKnownTypes.Value.ForString(post.Id.ToString()) } }, + AccountId = currentUser.Id.ToString(), + UserAgent = Request.Headers.UserAgent, + IpAddress = Request.HttpContext.Connection.RemoteIpAddress?.ToString() + }); return post; } @@ -282,31 +319,34 @@ public class PostController( [RequiredPermission("global", "posts.react")] public async Task> ReactPost(Guid id, [FromBody] PostReactionRequest request) { - HttpContext.Items.TryGetValue("CurrentUser", out var currentUserValue); - if (currentUserValue is not Account currentUser) return Unauthorized(); - var userFriends = await rels.ListAccountFriends(currentUser); - var userPublishers = await pub.GetUserPublishers(currentUser.Id); + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); + + var friendsResponse = + await accounts.ListFriendsAsync(new ListRelationshipSimpleRequest + { AccountId = currentUser.Id.ToString() }); + var userFriends = friendsResponse.AccountsId.Select(Guid.Parse).ToList(); + var userPublishers = await pub.GetUserPublishers(Guid.Parse(currentUser.Id)); var post = await db.Posts .Where(e => e.Id == id) .Include(e => e.Publisher) - .ThenInclude(e => e.Account) .FilterWithVisibility(currentUser, userFriends, userPublishers) .FirstOrDefaultAsync(); if (post is null) return NotFound(); - var isSelfReact = post.Publisher.AccountId is not null && post.Publisher.AccountId == currentUser.Id; + var accountId = Guid.Parse(currentUser.Id); + var isSelfReact = post.Publisher.AccountId is not null && post.Publisher.AccountId == accountId; var isExistingReaction = await db.PostReactions .AnyAsync(r => r.PostId == post.Id && r.Symbol == request.Symbol && - r.AccountId == currentUser.Id); + r.AccountId == accountId); var reaction = new PostReaction { Symbol = request.Symbol, Attitude = request.Attitude, PostId = post.Id, - AccountId = currentUser.Id + AccountId = accountId }; var isRemoving = await ps.ModifyPostVotes( post, @@ -318,10 +358,18 @@ public class PostController( if (isRemoving) return NoContent(); - als.CreateActionLogFromRequest( - ActionLogType.PostReact, - new Dictionary { { "post_id", post.Id }, { "reaction", request.Symbol } }, Request - ); + _ = als.CreateActionLogAsync(new CreateActionLogRequest + { + Action = ActionLogType.PostReact, + Meta = + { + { "post_id", Google.Protobuf.WellKnownTypes.Value.ForString(post.Id.ToString()) }, + { "reaction", Google.Protobuf.WellKnownTypes.Value.ForString(request.Symbol) } + }, + AccountId = currentUser.Id.ToString(), + UserAgent = Request.Headers.UserAgent, + IpAddress = Request.HttpContext.Connection.RemoteIpAddress?.ToString() + }); return Ok(reaction); } @@ -342,7 +390,8 @@ public class PostController( .FirstOrDefaultAsync(); if (post is null) return NotFound(); - if (!await pub.IsMemberWithRole(post.Publisher.Id, currentUser.Id, PublisherMemberRole.Editor)) + var accountId = Guid.Parse(currentUser.Id); + if (!await pub.IsMemberWithRole(post.Publisher.Id, accountId, PublisherMemberRole.Editor)) return StatusCode(403, "You need at least be an editor to edit this publisher's post."); if (request.Title is not null) post.Title = request.Title; @@ -367,10 +416,14 @@ public class PostController( return BadRequest(err.Message); } - als.CreateActionLogFromRequest( - ActionLogType.PostUpdate, - new Dictionary { { "post_id", post.Id } }, Request - ); + _ = als.CreateActionLogAsync(new CreateActionLogRequest + { + Action = ActionLogType.PostUpdate, + Meta = { { "post_id", Google.Protobuf.WellKnownTypes.Value.ForString(post.Id.ToString()) } }, + AccountId = currentUser.Id.ToString(), + UserAgent = Request.Headers.UserAgent, + IpAddress = Request.HttpContext.Connection.RemoteIpAddress?.ToString() + }); return Ok(post); } @@ -386,15 +439,19 @@ public class PostController( .FirstOrDefaultAsync(); if (post is null) return NotFound(); - if (!await pub.IsMemberWithRole(post.Publisher.Id, currentUser.Id, PublisherMemberRole.Editor)) + if (!await pub.IsMemberWithRole(post.Publisher.Id, Guid.Parse(currentUser.Id), PublisherMemberRole.Editor)) return StatusCode(403, "You need at least be an editor to delete the publisher's post."); await ps.DeletePostAsync(post); - als.CreateActionLogFromRequest( - ActionLogType.PostDelete, - new Dictionary { { "post_id", post.Id } }, Request - ); + _ = als.CreateActionLogAsync(new CreateActionLogRequest + { + Action = ActionLogType.PostDelete, + Meta = { { "post_id", Google.Protobuf.WellKnownTypes.Value.ForString(post.Id.ToString()) } }, + AccountId = currentUser.Id.ToString(), + UserAgent = Request.Headers.UserAgent, + IpAddress = Request.HttpContext.Connection.RemoteIpAddress?.ToString() + }); return NoContent(); } diff --git a/DysonNetwork.Sphere/Post/PostService.cs b/DysonNetwork.Sphere/Post/PostService.cs index 6a208ac..d8f60e0 100644 --- a/DysonNetwork.Sphere/Post/PostService.cs +++ b/DysonNetwork.Sphere/Post/PostService.cs @@ -136,7 +136,6 @@ public partial class PostService( // Create file references for each attachment if (post.Attachments.Count != 0) { - var postResourceId = $"post:{post.Id}"; var request = new CreateReferenceBatchRequest { Usage = PostFileUsageIdentifier, diff --git a/DysonNetwork.Sphere/Publisher/Publisher.cs b/DysonNetwork.Sphere/Publisher/Publisher.cs index 30975e4..c8bfc81 100644 --- a/DysonNetwork.Sphere/Publisher/Publisher.cs +++ b/DysonNetwork.Sphere/Publisher/Publisher.cs @@ -2,9 +2,11 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using System.Text.Json.Serialization; using DysonNetwork.Shared.Data; +using DysonNetwork.Shared.Proto; using DysonNetwork.Sphere.Post; using Microsoft.EntityFrameworkCore; using NodaTime; +using VerificationMark = DysonNetwork.Shared.Data.VerificationMark; namespace DysonNetwork.Sphere.Publisher; @@ -43,6 +45,7 @@ public class Publisher : ModelBase, IIdentifiedResource public Guid? AccountId { get; set; } public Guid? RealmId { get; set; } [JsonIgnore] public Realm.Realm? Realm { get; set; } + [NotMapped] public Account? Account { get; set; } public string ResourceIdentifier => $"publisher:{Id}"; } diff --git a/DysonNetwork.Sphere/Publisher/PublisherController.cs b/DysonNetwork.Sphere/Publisher/PublisherController.cs index aaa2ba8..e541d68 100644 --- a/DysonNetwork.Sphere/Publisher/PublisherController.cs +++ b/DysonNetwork.Sphere/Publisher/PublisherController.cs @@ -1,8 +1,8 @@ using System.ComponentModel.DataAnnotations; -using DysonNetwork.Sphere.Account; -using DysonNetwork.Sphere.Permission; +using DysonNetwork.Shared.Auth; +using DysonNetwork.Shared.Data; +using DysonNetwork.Shared.Proto; using DysonNetwork.Sphere.Realm; -using DysonNetwork.Sphere.Storage; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; @@ -15,8 +15,11 @@ namespace DysonNetwork.Sphere.Publisher; public class PublisherController( AppDatabase db, PublisherService ps, - FileReferenceService fileRefService, - ActionLogService als) + AccountService.AccountServiceClient accounts, + FileService.FileServiceClient files, + FileReferenceService.FileReferenceServiceClient fileRefs, + ActionLogService.ActionLogServiceClient als +) : ControllerBase { [HttpGet("{name}")] @@ -28,10 +31,9 @@ public class PublisherController( if (publisher is null) return NotFound(); if (publisher.AccountId is null) return Ok(publisher); - var account = await db.Accounts - .Where(a => a.Id == publisher.AccountId) - .Include(a => a.Profile) - .FirstOrDefaultAsync(); + var account = await accounts.GetAccountAsync( + new GetAccountRequest { Id = publisher.AccountId.Value.ToString() } + ); publisher.Account = account; return Ok(publisher); @@ -50,10 +52,10 @@ public class PublisherController( public async Task>> ListManagedPublishers() { if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); - var userId = currentUser.Id; + var accountId = Guid.Parse(currentUser.Id); var members = await db.PublisherMembers - .Where(m => m.AccountId == userId) + .Where(m => m.AccountId == accountId) .Where(m => m.JoinedAt != null) .Include(e => e.Publisher) .ToListAsync(); @@ -66,10 +68,10 @@ public class PublisherController( public async Task>> ListInvites() { if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); - var userId = currentUser.Id; + var accountId = Guid.Parse(currentUser.Id); var members = await db.PublisherMembers - .Where(m => m.AccountId == userId) + .Where(m => m.AccountId == accountId) .Where(m => m.JoinedAt == null) .Include(e => e.Publisher) .ToListAsync(); @@ -89,22 +91,23 @@ public class PublisherController( [FromBody] PublisherMemberRequest request) { if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); - var userId = currentUser.Id; + var accountId = Guid.Parse(currentUser.Id); - var relatedUser = await db.Accounts.FindAsync(request.RelatedUserId); - if (relatedUser is null) return BadRequest("Related user was not found"); + var relatedUser = + await accounts.GetAccountAsync(new GetAccountRequest { Id = request.RelatedUserId.ToString() }); + if (relatedUser == null) return BadRequest("Related user was not found"); var publisher = await db.Publishers .Where(p => p.Name == name) .FirstOrDefaultAsync(); if (publisher is null) return NotFound(); - if (!await ps.IsMemberWithRole(publisher.Id, currentUser.Id, request.Role)) + if (!await ps.IsMemberWithRole(publisher.Id, accountId, request.Role)) return StatusCode(403, "You cannot invite member has higher permission than yours."); var newMember = new PublisherMember { - AccountId = relatedUser.Id, + AccountId = Guid.Parse(relatedUser.Id), PublisherId = publisher.Id, Role = request.Role, }; @@ -112,14 +115,18 @@ public class PublisherController( db.PublisherMembers.Add(newMember); await db.SaveChangesAsync(); - als.CreateActionLogFromRequest( - ActionLogType.PublisherMemberInvite, - new Dictionary + _ = als.CreateActionLogAsync(new CreateActionLogRequest + { + Action = "publishers.members.invite", + Meta = { - { "publisher_id", publisher.Id }, - { "account_id", relatedUser.Id } - }, Request - ); + { "publisher_id", Google.Protobuf.WellKnownTypes.Value.ForString(publisher.Id.ToString()) }, + { "account_id", Google.Protobuf.WellKnownTypes.Value.ForString(relatedUser.Id.ToString()) } + }, + AccountId = currentUser.Id, + UserAgent = Request.Headers.UserAgent, + IpAddress = Request.HttpContext.Connection.RemoteIpAddress?.ToString() + }); return Ok(newMember); } @@ -129,10 +136,10 @@ public class PublisherController( public async Task> AcceptMemberInvite(string name) { if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); - var userId = currentUser.Id; + var accountId = Guid.Parse(currentUser.Id); var member = await db.PublisherMembers - .Where(m => m.AccountId == userId) + .Where(m => m.AccountId == accountId) .Where(m => m.Publisher.Name == name) .Where(m => m.JoinedAt == null) .FirstOrDefaultAsync(); @@ -142,10 +149,18 @@ public class PublisherController( db.Update(member); await db.SaveChangesAsync(); - als.CreateActionLogFromRequest( - ActionLogType.PublisherMemberJoin, - new Dictionary { { "account_id", member.AccountId } }, Request - ); + _ = als.CreateActionLogAsync(new CreateActionLogRequest + { + Action = "publishers.members.join", + Meta = + { + { "publisher_id", Google.Protobuf.WellKnownTypes.Value.ForString(member.PublisherId.ToString()) }, + { "account_id", Google.Protobuf.WellKnownTypes.Value.ForString(member.AccountId.ToString()) } + }, + AccountId = currentUser.Id, + UserAgent = Request.Headers.UserAgent, + IpAddress = Request.HttpContext.Connection.RemoteIpAddress?.ToString() + }); return Ok(member); } @@ -155,10 +170,10 @@ public class PublisherController( public async Task DeclineMemberInvite(string name) { if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); - var userId = currentUser.Id; + var accountId = Guid.Parse(currentUser.Id); var member = await db.PublisherMembers - .Where(m => m.AccountId == userId) + .Where(m => m.AccountId == accountId) .Where(m => m.Publisher.Name == name) .Where(m => m.JoinedAt == null) .FirstOrDefaultAsync(); @@ -167,10 +182,18 @@ public class PublisherController( db.PublisherMembers.Remove(member); await db.SaveChangesAsync(); - als.CreateActionLogFromRequest( - ActionLogType.PublisherMemberLeave, - new Dictionary { { "account_id", member.AccountId } }, Request - ); + _ = als.CreateActionLogAsync(new CreateActionLogRequest + { + Action = "publishers.members.decline", + Meta = + { + { "publisher_id", Google.Protobuf.WellKnownTypes.Value.ForString(member.PublisherId.ToString()) }, + { "account_id", Google.Protobuf.WellKnownTypes.Value.ForString(member.AccountId.ToString()) } + }, + AccountId = currentUser.Id, + UserAgent = Request.Headers.UserAgent, + IpAddress = Request.HttpContext.Connection.RemoteIpAddress?.ToString() + }); return NoContent(); } @@ -190,21 +213,27 @@ public class PublisherController( .Where(m => m.AccountId == memberId) .Where(m => m.PublisherId == publisher.Id) .FirstOrDefaultAsync(); + var accountId = Guid.Parse(currentUser.Id); if (member is null) return NotFound("Member was not found"); - if (!await ps.IsMemberWithRole(publisher.Id, currentUser.Id, PublisherMemberRole.Manager)) + if (!await ps.IsMemberWithRole(publisher.Id, accountId, PublisherMemberRole.Manager)) return StatusCode(403, "You need at least be a manager to remove members from this publisher."); db.PublisherMembers.Remove(member); await db.SaveChangesAsync(); - als.CreateActionLogFromRequest( - ActionLogType.PublisherMemberKick, - new Dictionary + _ = als.CreateActionLogAsync(new CreateActionLogRequest + { + Action = "publishers.members.kick", + Meta = { - { "publisher_id", publisher.Id }, - { "account_id", memberId } - }, Request - ); + { "publisher_id", Google.Protobuf.WellKnownTypes.Value.ForString(publisher.Id.ToString()) }, + { "account_id", Google.Protobuf.WellKnownTypes.Value.ForString(memberId.ToString()) }, + { "kicked_by", Google.Protobuf.WellKnownTypes.Value.ForString(currentUser.Id) } + }, + AccountId = currentUser.Id, + UserAgent = Request.Headers.UserAgent, + IpAddress = Request.HttpContext.Connection.RemoteIpAddress?.ToString() + }); return NoContent(); } @@ -238,17 +267,25 @@ public class PublisherController( "your name firstly to get your name back." ); - CloudFile? picture = null, background = null; + CloudFileReferenceObject? picture = null, background = null; if (request.PictureId is not null) { - picture = await db.Files.Where(f => f.Id == request.PictureId).FirstOrDefaultAsync(); - if (picture is null) return BadRequest("Invalid picture id, unable to find the file on cloud."); + var queryResult = await files.GetFileAsync( + new GetFileRequest { Id = request.PictureId } + ); + if (queryResult is null) + throw new InvalidOperationException("Invalid picture id, unable to find the file on cloud."); + picture = CloudFileReferenceObject.FromProtoValue(queryResult); } if (request.BackgroundId is not null) { - background = await db.Files.Where(f => f.Id == request.BackgroundId).FirstOrDefaultAsync(); - if (background is null) return BadRequest("Invalid background id, unable to find the file on cloud."); + var queryResult = await files.GetFileAsync( + new GetFileRequest { Id = request.BackgroundId } + ); + if (queryResult is null) + throw new InvalidOperationException("Invalid background id, unable to find the file on cloud."); + background = CloudFileReferenceObject.FromProtoValue(queryResult); } var publisher = await ps.CreateIndividualPublisher( @@ -260,10 +297,19 @@ public class PublisherController( background ); - als.CreateActionLogFromRequest( - ActionLogType.PublisherCreate, - new Dictionary { { "publisher_id", publisher.Id } }, Request - ); + _ = als.CreateActionLogAsync(new CreateActionLogRequest + { + Action = "publishers.create", + Meta = + { + { "publisher_id", Google.Protobuf.WellKnownTypes.Value.ForString(publisher.Id.ToString()) }, + { "publisher_name", Google.Protobuf.WellKnownTypes.Value.ForString(publisher.Name) }, + { "publisher_type", Google.Protobuf.WellKnownTypes.Value.ForString("Individual") } + }, + AccountId = currentUser.Id, + UserAgent = Request.Headers.UserAgent, + IpAddress = Request.HttpContext.Connection.RemoteIpAddress?.ToString() + }); return Ok(publisher); } @@ -279,9 +325,10 @@ public class PublisherController( var realm = await db.Realms.FirstOrDefaultAsync(r => r.Slug == realmSlug); if (realm == null) return NotFound("Realm not found"); + var accountId = Guid.Parse(currentUser.Id); var isAdmin = await db.RealmMembers .AnyAsync(m => - m.RealmId == realm.Id && m.AccountId == currentUser.Id && m.Role >= RealmMemberRole.Moderator); + m.RealmId == realm.Id && m.AccountId == accountId && m.Role >= RealmMemberRole.Moderator); if (!isAdmin) return StatusCode(403, "You need to be a moderator of the realm to create an organization publisher"); @@ -292,17 +339,25 @@ public class PublisherController( if (duplicateNameCount > 0) return BadRequest("The name you requested has already been taken"); - CloudFile? picture = null, background = null; + CloudFileReferenceObject? picture = null, background = null; if (request.PictureId is not null) { - picture = await db.Files.Where(f => f.Id == request.PictureId).FirstOrDefaultAsync(); - if (picture is null) return BadRequest("Invalid picture id, unable to find the file on cloud."); + var queryResult = await files.GetFileAsync( + new GetFileRequest { Id = request.PictureId } + ); + if (queryResult is null) + throw new InvalidOperationException("Invalid picture id, unable to find the file on cloud."); + picture = CloudFileReferenceObject.FromProtoValue(queryResult); } if (request.BackgroundId is not null) { - background = await db.Files.Where(f => f.Id == request.BackgroundId).FirstOrDefaultAsync(); - if (background is null) return BadRequest("Invalid background id, unable to find the file on cloud."); + var queryResult = await files.GetFileAsync( + new GetFileRequest { Id = request.BackgroundId } + ); + if (queryResult is null) + throw new InvalidOperationException("Invalid background id, unable to find the file on cloud."); + background = CloudFileReferenceObject.FromProtoValue(queryResult); } var publisher = await ps.CreateOrganizationPublisher( @@ -315,10 +370,20 @@ public class PublisherController( background ); - als.CreateActionLogFromRequest( - ActionLogType.PublisherCreate, - new Dictionary { { "publisher_id", publisher.Id } }, Request - ); + _ = als.CreateActionLogAsync(new CreateActionLogRequest + { + Action = "publishers.create", + Meta = + { + { "publisher_id", Google.Protobuf.WellKnownTypes.Value.ForString(publisher.Id.ToString()) }, + { "publisher_name", Google.Protobuf.WellKnownTypes.Value.ForString(publisher.Name) }, + { "publisher_type", Google.Protobuf.WellKnownTypes.Value.ForString("Organization") }, + { "realm_slug", Google.Protobuf.WellKnownTypes.Value.ForString(realm.Slug) } + }, + AccountId = currentUser.Id, + UserAgent = Request.Headers.UserAgent, + IpAddress = Request.HttpContext.Connection.RemoteIpAddress?.ToString() + }); return Ok(publisher); } @@ -329,7 +394,7 @@ public class PublisherController( public async Task> UpdatePublisher(string name, PublisherRequest request) { if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); - var userId = currentUser.Id; + var accountId = Guid.Parse(currentUser.Id); var publisher = await db.Publishers .Where(p => p.Name == name) @@ -337,7 +402,7 @@ public class PublisherController( if (publisher is null) return NotFound(); var member = await db.PublisherMembers - .Where(m => m.AccountId == userId) + .Where(m => m.AccountId == accountId) .Where(m => m.PublisherId == publisher.Id) .FirstOrDefaultAsync(); if (member is null) return StatusCode(403, "You are not even a member of the targeted publisher."); @@ -349,54 +414,81 @@ public class PublisherController( if (request.Bio is not null) publisher.Bio = request.Bio; if (request.PictureId is not null) { - var picture = await db.Files.Where(f => f.Id == request.PictureId).FirstOrDefaultAsync(); - if (picture is null) return BadRequest("Invalid picture id."); + var queryResult = await files.GetFileAsync( + new GetFileRequest { Id = request.PictureId } + ); + if (queryResult is null) + throw new InvalidOperationException("Invalid picture id, unable to find the file on cloud."); + var picture = CloudFileReferenceObject.FromProtoValue(queryResult); // Remove old references for the publisher picture if (publisher.Picture is not null) - { - await fileRefService.DeleteResourceReferencesAsync(publisher.ResourceIdentifier, "publisher.picture"); - } + await fileRefs.DeleteResourceReferencesAsync(new DeleteResourceReferencesRequest + { + ResourceId = publisher.ResourceIdentifier + }); - publisher.Picture = picture.ToReferenceObject(); + publisher.Picture = picture; - // Create a new reference - await fileRefService.CreateReferenceAsync( - picture.Id, - "publisher.picture", - publisher.ResourceIdentifier + await fileRefs.CreateReferenceAsync( + new CreateReferenceRequest + { + FileId = picture.Id, + Usage = "publisher.picture", + ResourceId = publisher.ResourceIdentifier + } ); } if (request.BackgroundId is not null) { - var background = await db.Files.Where(f => f.Id == request.BackgroundId).FirstOrDefaultAsync(); - if (background is null) return BadRequest("Invalid background id."); + var queryResult = await files.GetFileAsync( + new GetFileRequest { Id = request.BackgroundId } + ); + if (queryResult is null) + throw new InvalidOperationException("Invalid background id, unable to find the file on cloud."); + var background = CloudFileReferenceObject.FromProtoValue(queryResult); // Remove old references for the publisher background if (publisher.Background is not null) { - await fileRefService.DeleteResourceReferencesAsync(publisher.ResourceIdentifier, - "publisher.background"); + await fileRefs.DeleteResourceReferencesAsync(new DeleteResourceReferencesRequest + { + ResourceId = publisher.ResourceIdentifier + }); } - publisher.Background = background.ToReferenceObject(); + publisher.Background = background; - // Create a new reference - await fileRefService.CreateReferenceAsync( - background.Id, - "publisher.background", - publisher.ResourceIdentifier + await fileRefs.CreateReferenceAsync( + new CreateReferenceRequest + { + FileId = background.Id, + Usage = "publisher.background", + ResourceId = publisher.ResourceIdentifier + } ); } db.Update(publisher); await db.SaveChangesAsync(); - als.CreateActionLogFromRequest( - ActionLogType.PublisherUpdate, - new Dictionary { { "publisher_id", publisher.Id } }, Request - ); + _ = als.CreateActionLogAsync(new CreateActionLogRequest + { + Action = "publishers.update", + Meta = + { + { "publisher_id", Google.Protobuf.WellKnownTypes.Value.ForString(publisher.Id.ToString()) }, + { "name_updated", Google.Protobuf.WellKnownTypes.Value.ForBool(!string.IsNullOrEmpty(request.Name)) }, + { "nick_updated", Google.Protobuf.WellKnownTypes.Value.ForBool(!string.IsNullOrEmpty(request.Nick)) }, + { "bio_updated", Google.Protobuf.WellKnownTypes.Value.ForBool(!string.IsNullOrEmpty(request.Bio)) }, + { "picture_updated", Google.Protobuf.WellKnownTypes.Value.ForBool(request.PictureId != null) }, + { "background_updated", Google.Protobuf.WellKnownTypes.Value.ForBool(request.BackgroundId != null) } + }, + AccountId = currentUser.Id, + UserAgent = Request.Headers.UserAgent, + IpAddress = Request.HttpContext.Connection.RemoteIpAddress?.ToString() + }); return Ok(publisher); } @@ -406,7 +498,7 @@ public class PublisherController( public async Task> DeletePublisher(string name) { if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); - var userId = currentUser.Id; + var accountId = Guid.Parse(currentUser.Id); var publisher = await db.Publishers .Where(p => p.Name == name) @@ -416,7 +508,7 @@ public class PublisherController( if (publisher is null) return NotFound(); var member = await db.PublisherMembers - .Where(m => m.AccountId == userId) + .Where(m => m.AccountId == accountId) .Where(m => m.PublisherId == publisher.Id) .FirstOrDefaultAsync(); if (member is null) return StatusCode(403, "You are not even a member of the targeted publisher."); @@ -426,15 +518,26 @@ public class PublisherController( var publisherResourceId = $"publisher:{publisher.Id}"; // Delete all file references for this publisher - await fileRefService.DeleteResourceReferencesAsync(publisherResourceId); + await fileRefs.DeleteResourceReferencesAsync( + new DeleteResourceReferencesRequest { ResourceId = publisherResourceId } + ); db.Publishers.Remove(publisher); await db.SaveChangesAsync(); - als.CreateActionLogFromRequest( - ActionLogType.PublisherDelete, - new Dictionary { { "publisher_id", publisher.Id } }, Request - ); + _ = als.CreateActionLogAsync(new CreateActionLogRequest + { + Action = "publishers.delete", + Meta = + { + { "publisher_id", Google.Protobuf.WellKnownTypes.Value.ForString(publisher.Id.ToString()) }, + { "publisher_name", Google.Protobuf.WellKnownTypes.Value.ForString(publisher.Name) }, + { "publisher_type", Google.Protobuf.WellKnownTypes.Value.ForString(publisher.Type.ToString()) } + }, + AccountId = currentUser.Id, + UserAgent = Request.Headers.UserAgent, + IpAddress = Request.HttpContext.Connection.RemoteIpAddress?.ToString() + }); return NoContent(); } @@ -451,11 +554,9 @@ public class PublisherController( .FirstOrDefaultAsync(); if (publisher is null) return NotFound(); - IQueryable query = db.PublisherMembers + var query = db.PublisherMembers .Where(m => m.PublisherId == publisher.Id) - .Where(m => m.JoinedAt != null) - .Include(m => m.Account) - .ThenInclude(m => m.Profile); + .Where(m => m.JoinedAt != null); var total = await query.CountAsync(); Response.Headers["X-Total"] = total.ToString(); @@ -474,7 +575,7 @@ public class PublisherController( public async Task> GetCurrentIdentity(string name) { if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); - var userId = currentUser.Id; + var accountId = Guid.Parse(currentUser.Id); var publisher = await db.Publishers .Where(p => p.Name == name) @@ -482,10 +583,8 @@ public class PublisherController( if (publisher is null) return NotFound(); var member = await db.PublisherMembers - .Where(m => m.AccountId == userId) + .Where(m => m.AccountId == accountId) .Where(m => m.PublisherId == publisher.Id) - .Include(m => m.Account) - .ThenInclude(m => m.Profile) .FirstOrDefaultAsync(); if (member is null) return NotFound(); diff --git a/DysonNetwork.Sphere/Publisher/PublisherSubscriptionController.cs b/DysonNetwork.Sphere/Publisher/PublisherSubscriptionController.cs index b5270c4..9220111 100644 --- a/DysonNetwork.Sphere/Publisher/PublisherSubscriptionController.cs +++ b/DysonNetwork.Sphere/Publisher/PublisherSubscriptionController.cs @@ -1,3 +1,4 @@ +using DysonNetwork.Shared.Proto; using DysonNetwork.Sphere.Post; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; @@ -37,7 +38,7 @@ public class PublisherSubscriptionController( if (publisher == null) return NotFound("Publisher not found"); - var isSubscribed = await subs.SubscriptionExistsAsync(currentUser.Id, publisher.Id); + var isSubscribed = await subs.SubscriptionExistsAsync(Guid.Parse(currentUser.Id), publisher.Id); return new SubscriptionStatusResponse { @@ -63,7 +64,7 @@ public class PublisherSubscriptionController( try { var subscription = await subs.CreateSubscriptionAsync( - currentUser.Id, + Guid.Parse(currentUser.Id), publisher.Id, request.Tier ?? 0 ); @@ -88,7 +89,7 @@ public class PublisherSubscriptionController( if (publisher == null) return NotFound("Publisher not found"); - var success = await subs.CancelSubscriptionAsync(currentUser.Id, publisher.Id); + var success = await subs.CancelSubscriptionAsync(Guid.Parse(currentUser.Id), publisher.Id); if (success) return Ok(new { message = "Subscription cancelled successfully" }); @@ -106,7 +107,7 @@ public class PublisherSubscriptionController( { if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); - var subscriptions = await subs.GetAccountSubscriptionsAsync(currentUser.Id); + var subscriptions = await subs.GetAccountSubscriptionsAsync(Guid.Parse(currentUser.Id)); return subscriptions; } } \ No newline at end of file diff --git a/DysonNetwork.Sphere/Realm/RealmController.cs b/DysonNetwork.Sphere/Realm/RealmController.cs index adc0603..ca73f2b 100644 --- a/DysonNetwork.Sphere/Realm/RealmController.cs +++ b/DysonNetwork.Sphere/Realm/RealmController.cs @@ -1,6 +1,4 @@ using System.ComponentModel.DataAnnotations; -using DysonNetwork.Sphere.Account; -using DysonNetwork.Sphere.Storage; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Authorization; using Microsoft.EntityFrameworkCore; diff --git a/DysonNetwork.Sphere/Startup/ServiceCollectionExtensions.cs b/DysonNetwork.Sphere/Startup/ServiceCollectionExtensions.cs index 31d083f..502039f 100644 --- a/DysonNetwork.Sphere/Startup/ServiceCollectionExtensions.cs +++ b/DysonNetwork.Sphere/Startup/ServiceCollectionExtensions.cs @@ -178,7 +178,7 @@ public static class ServiceCollectionExtensions services.AddScoped(); services.AddScoped(); services.AddScoped(); - services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); From 8fbc81cab9a58fa26c5f887b80860d14f384e3e0 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Tue, 15 Jul 2025 16:10:57 +0800 Subject: [PATCH 20/42] :sparkles: Done mixing --- ...250715080004_ReinitalMigration.Designer.cs | 190 ++ .../20250715080004_ReinitalMigration.cs | 36 + .../Migrations/AppDatabaseModelSnapshot.cs | 2 +- DysonNetwork.Drive/Program.cs | 2 +- ...250715075623_ReinitalMigration.Designer.cs | 1967 +++++++++++++++++ .../20250715075623_ReinitalMigration.cs | 40 + .../Migrations/AppDatabaseModelSnapshot.cs | 10 - DysonNetwork.Pass/Program.cs | 1 - .../Safety/AbuseReportController.cs | 12 +- .../Safety/SafetyService.cs | 4 +- DysonNetwork.Pass/Wallet/PaymentService.cs | 2 +- .../Wallet/SubscriptionService.cs | 2 +- DysonNetwork.Pusher/Program.cs | 2 +- DysonNetwork.Shared/Auth/Startup.cs | 3 +- DysonNetwork.Sphere/Chat/ChatRoom.cs | 4 +- .../Chat/ChatRoomController.cs | 4 +- .../Chat/ChatRoomController.cs.bak | 947 -------- DysonNetwork.Sphere/Chat/ChatService.cs | 1 + .../Developer/DeveloperController.cs | 2 +- DysonNetwork.Sphere/Post/PostController.cs | 2 +- DysonNetwork.Sphere/Program.cs | 15 +- .../Publisher/PublisherSubscriptionService.cs | 46 +- DysonNetwork.Sphere/Realm/Realm.cs | 5 +- .../Realm/RealmChatController.cs | 4 +- DysonNetwork.Sphere/Realm/RealmController.cs | 416 ++-- .../Realm/RealmController.cs.bak | 696 ++++++ DysonNetwork.Sphere/Realm/RealmService.cs | 34 +- .../Startup/ApplicationConfiguration.cs | 10 +- .../Startup/ScheduledJobsConfiguration.cs | 69 +- .../Startup/ServiceCollectionExtensions.cs | 21 - .../Sticker/StickerController.cs | 2 +- DysonNetwork.Sphere/Sticker/StickerService.cs | 43 +- .../WebReader/WebReaderController.cs | 2 +- DysonNetwork.Sphere/appsettings.json | 96 +- 34 files changed, 3314 insertions(+), 1378 deletions(-) create mode 100644 DysonNetwork.Drive/Migrations/20250715080004_ReinitalMigration.Designer.cs create mode 100644 DysonNetwork.Drive/Migrations/20250715080004_ReinitalMigration.cs create mode 100644 DysonNetwork.Pass/Migrations/20250715075623_ReinitalMigration.Designer.cs create mode 100644 DysonNetwork.Pass/Migrations/20250715075623_ReinitalMigration.cs rename {DysonNetwork.Sphere => DysonNetwork.Pass}/Safety/AbuseReportController.cs (91%) rename {DysonNetwork.Sphere => DysonNetwork.Pass}/Safety/SafetyService.cs (97%) delete mode 100644 DysonNetwork.Sphere/Chat/ChatRoomController.cs.bak create mode 100644 DysonNetwork.Sphere/Realm/RealmController.cs.bak diff --git a/DysonNetwork.Drive/Migrations/20250715080004_ReinitalMigration.Designer.cs b/DysonNetwork.Drive/Migrations/20250715080004_ReinitalMigration.Designer.cs new file mode 100644 index 0000000..a450ff7 --- /dev/null +++ b/DysonNetwork.Drive/Migrations/20250715080004_ReinitalMigration.Designer.cs @@ -0,0 +1,190 @@ +// +using System; +using System.Collections.Generic; +using DysonNetwork.Drive; +using DysonNetwork.Shared.Data; +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.Drive.Migrations +{ + [DbContext(typeof(AppDatabase))] + [Migration("20250715080004_ReinitalMigration")] + partial class ReinitalMigration + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.7") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "postgis"); + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("DysonNetwork.Drive.Storage.CloudFile", b => + { + b.Property("Id") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .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("Description") + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("description"); + + b.Property>("FileMeta") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("file_meta"); + + b.Property("HasCompression") + .HasColumnType("boolean") + .HasColumnName("has_compression"); + + b.Property("Hash") + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("hash"); + + b.Property("IsMarkedRecycle") + .HasColumnType("boolean") + .HasColumnName("is_marked_recycle"); + + b.Property("MimeType") + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("mime_type"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("name"); + + b.Property>("SensitiveMarks") + .HasColumnType("jsonb") + .HasColumnName("sensitive_marks"); + + b.Property("Size") + .HasColumnType("bigint") + .HasColumnName("size"); + + b.Property("StorageId") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("storage_id"); + + b.Property("StorageUrl") + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("storage_url"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.Property("UploadedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("uploaded_at"); + + b.Property("UploadedTo") + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("uploaded_to"); + + b.Property>("UserMeta") + .HasColumnType("jsonb") + .HasColumnName("user_meta"); + + b.HasKey("Id") + .HasName("pk_files"); + + b.ToTable("files", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Drive.Storage.CloudFileReference", 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("ExpiredAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("expired_at"); + + b.Property("FileId") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("file_id"); + + b.Property("ResourceId") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("resource_id"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.Property("Usage") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("usage"); + + b.HasKey("Id") + .HasName("pk_file_references"); + + b.HasIndex("FileId") + .HasDatabaseName("ix_file_references_file_id"); + + b.ToTable("file_references", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Drive.Storage.CloudFileReference", b => + { + b.HasOne("DysonNetwork.Drive.Storage.CloudFile", "File") + .WithMany() + .HasForeignKey("FileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_file_references_files_file_id"); + + b.Navigation("File"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/DysonNetwork.Drive/Migrations/20250715080004_ReinitalMigration.cs b/DysonNetwork.Drive/Migrations/20250715080004_ReinitalMigration.cs new file mode 100644 index 0000000..da08cf3 --- /dev/null +++ b/DysonNetwork.Drive/Migrations/20250715080004_ReinitalMigration.cs @@ -0,0 +1,36 @@ +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace DysonNetwork.Drive.Migrations +{ + /// + public partial class ReinitalMigration : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn>( + name: "file_meta", + table: "files", + type: "jsonb", + nullable: false, + oldClrType: typeof(Dictionary), + oldType: "jsonb", + oldNullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn>( + name: "file_meta", + table: "files", + type: "jsonb", + nullable: true, + oldClrType: typeof(Dictionary), + oldType: "jsonb"); + } + } +} diff --git a/DysonNetwork.Drive/Migrations/AppDatabaseModelSnapshot.cs b/DysonNetwork.Drive/Migrations/AppDatabaseModelSnapshot.cs index 62b7a87..039bdfc 100644 --- a/DysonNetwork.Drive/Migrations/AppDatabaseModelSnapshot.cs +++ b/DysonNetwork.Drive/Migrations/AppDatabaseModelSnapshot.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; using DysonNetwork.Drive; -using DysonNetwork.Drive.Storage; using DysonNetwork.Shared.Data; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; @@ -52,6 +51,7 @@ namespace DysonNetwork.Drive.Migrations .HasColumnName("description"); b.Property>("FileMeta") + .IsRequired() .HasColumnType("jsonb") .HasColumnName("file_meta"); diff --git a/DysonNetwork.Drive/Program.cs b/DysonNetwork.Drive/Program.cs index d538e61..a759e6f 100644 --- a/DysonNetwork.Drive/Program.cs +++ b/DysonNetwork.Drive/Program.cs @@ -17,7 +17,7 @@ builder.Services.AddAppServices(builder.Configuration); builder.Services.AddAppRateLimiting(); builder.Services.AddAppAuthentication(); builder.Services.AddAppSwagger(); -builder.Services.AddDysonAuth(builder.Configuration); +builder.Services.AddDysonAuth(); builder.Services.AddAppFileStorage(builder.Configuration); diff --git a/DysonNetwork.Pass/Migrations/20250715075623_ReinitalMigration.Designer.cs b/DysonNetwork.Pass/Migrations/20250715075623_ReinitalMigration.Designer.cs new file mode 100644 index 0000000..847168a --- /dev/null +++ b/DysonNetwork.Pass/Migrations/20250715075623_ReinitalMigration.Designer.cs @@ -0,0 +1,1967 @@ +// +using System; +using System.Collections.Generic; +using System.Text.Json; +using DysonNetwork.Pass; +using DysonNetwork.Pass.Account; +using DysonNetwork.Pass.Developer; +using DysonNetwork.Pass.Wallet; +using DysonNetwork.Shared.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using NetTopologySuite.Geometries; +using NodaTime; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace DysonNetwork.Pass.Migrations +{ + [DbContext(typeof(AppDatabase))] + [Migration("20250715075623_ReinitalMigration")] + partial class ReinitalMigration + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.7") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "postgis"); + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("DysonNetwork.Pass.Account.AbuseReport", 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.Pass.Account.Account", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("ActivatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("activated_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("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("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.Pass.Account.AccountAuthFactor", 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.Pass.Account.AccountBadge", 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.Pass.Account.AccountConnection", 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.Pass.Account.AccountContact", 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("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.Pass.Account.AccountProfile", 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("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("TimeZone") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("time_zone"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + 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.Pass.Account.ActionLog", 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("geometry") + .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.Pass.Account.CheckInResult", 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("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.Pass.Account.MagicSpell", 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.Pass.Account.Notification", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("AccountId") + .HasColumnType("uuid") + .HasColumnName("account_id"); + + b.Property("Content") + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .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>("Meta") + .HasColumnType("jsonb") + .HasColumnName("meta"); + + b.Property("Priority") + .HasColumnType("integer") + .HasColumnName("priority"); + + b.Property("Subtitle") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)") + .HasColumnName("subtitle"); + + b.Property("Title") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("title"); + + b.Property("Topic") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("topic"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.Property("ViewedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("viewed_at"); + + b.HasKey("Id") + .HasName("pk_notifications"); + + b.HasIndex("AccountId") + .HasDatabaseName("ix_notifications_account_id"); + + b.ToTable("notifications", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Pass.Account.NotificationPushSubscription", 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(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("device_id"); + + b.Property("DeviceToken") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("device_token"); + + b.Property("LastUsedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_used_at"); + + b.Property("Provider") + .HasColumnType("integer") + .HasColumnName("provider"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.HasKey("Id") + .HasName("pk_notification_push_subscriptions"); + + b.HasIndex("AccountId") + .HasDatabaseName("ix_notification_push_subscriptions_account_id"); + + b.HasIndex("DeviceToken", "DeviceId", "AccountId") + .IsUnique() + .HasDatabaseName("ix_notification_push_subscriptions_device_token_device_id_acco"); + + b.ToTable("notification_push_subscriptions", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Pass.Account.Relationship", 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.Pass.Account.Status", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("AccountId") + .HasColumnType("uuid") + .HasColumnName("account_id"); + + 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("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("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.Pass.Auth.AuthChallenge", 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") + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("device_id"); + + 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("geometry") + .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("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_challenges"); + + b.HasIndex("AccountId") + .HasDatabaseName("ix_auth_challenges_account_id"); + + b.ToTable("auth_challenges", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Pass.Auth.AuthSession", 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("ChallengeId") + .HasColumnType("uuid") + .HasColumnName("challenge_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("Label") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("label"); + + b.Property("LastGrantedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_granted_at"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.HasKey("Id") + .HasName("pk_auth_sessions"); + + b.HasIndex("AccountId") + .HasDatabaseName("ix_auth_sessions_account_id"); + + b.HasIndex("AppId") + .HasDatabaseName("ix_auth_sessions_app_id"); + + b.HasIndex("ChallengeId") + .HasDatabaseName("ix_auth_sessions_challenge_id"); + + b.ToTable("auth_sessions", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Pass.Developer.CustomApp", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("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") + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("description"); + + b.Property("Links") + .HasColumnType("jsonb") + .HasColumnName("links"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("name"); + + b.Property("OauthConfig") + .HasColumnType("jsonb") + .HasColumnName("oauth_config"); + + b.Property("Picture") + .HasColumnType("jsonb") + .HasColumnName("picture"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("slug"); + + b.Property("Status") + .HasColumnType("integer") + .HasColumnName("status"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.Property("Verification") + .HasColumnType("jsonb") + .HasColumnName("verification"); + + b.HasKey("Id") + .HasName("pk_custom_apps"); + + b.ToTable("custom_apps", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Pass.Developer.CustomAppSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("AppId") + .HasColumnType("uuid") + .HasColumnName("app_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("Description") + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("description"); + + b.Property("ExpiredAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("expired_at"); + + b.Property("IsOidc") + .HasColumnType("boolean") + .HasColumnName("is_oidc"); + + b.Property("Secret") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("secret"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.HasKey("Id") + .HasName("pk_custom_app_secrets"); + + b.HasIndex("AppId") + .HasDatabaseName("ix_custom_app_secrets_app_id"); + + b.ToTable("custom_app_secrets", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Pass.Permission.PermissionGroup", 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.Pass.Permission.PermissionGroupMember", 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.Pass.Permission.PermissionNode", 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("Area") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("area"); + + 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("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", "Area", "Actor") + .HasDatabaseName("ix_permission_nodes_key_area_actor"); + + b.ToTable("permission_nodes", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Pass.Wallet.Coupon", 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.Pass.Wallet.Order", 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("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.Pass.Wallet.Subscription", 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.ToTable("wallet_subscriptions", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Pass.Wallet.Transaction", 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.Pass.Wallet.Wallet", 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.Pass.Wallet.WalletPocket", 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.Pass.Account.AbuseReport", b => + { + b.HasOne("DysonNetwork.Pass.Account.Account", "Account") + .WithMany() + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_abuse_reports_accounts_account_id"); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("DysonNetwork.Pass.Account.AccountAuthFactor", b => + { + b.HasOne("DysonNetwork.Pass.Account.Account", "Account") + .WithMany("AuthFactors") + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_account_auth_factors_accounts_account_id"); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("DysonNetwork.Pass.Account.AccountBadge", b => + { + b.HasOne("DysonNetwork.Pass.Account.Account", "Account") + .WithMany("Badges") + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_badges_accounts_account_id"); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("DysonNetwork.Pass.Account.AccountConnection", b => + { + b.HasOne("DysonNetwork.Pass.Account.Account", "Account") + .WithMany("Connections") + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_account_connections_accounts_account_id"); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("DysonNetwork.Pass.Account.AccountContact", b => + { + b.HasOne("DysonNetwork.Pass.Account.Account", "Account") + .WithMany("Contacts") + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_account_contacts_accounts_account_id"); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("DysonNetwork.Pass.Account.AccountProfile", b => + { + b.HasOne("DysonNetwork.Pass.Account.Account", "Account") + .WithOne("Profile") + .HasForeignKey("DysonNetwork.Pass.Account.AccountProfile", "AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_account_profiles_accounts_account_id"); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("DysonNetwork.Pass.Account.ActionLog", b => + { + b.HasOne("DysonNetwork.Pass.Account.Account", "Account") + .WithMany() + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_action_logs_accounts_account_id"); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("DysonNetwork.Pass.Account.CheckInResult", b => + { + b.HasOne("DysonNetwork.Pass.Account.Account", "Account") + .WithMany() + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_account_check_in_results_accounts_account_id"); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("DysonNetwork.Pass.Account.MagicSpell", b => + { + b.HasOne("DysonNetwork.Pass.Account.Account", "Account") + .WithMany() + .HasForeignKey("AccountId") + .HasConstraintName("fk_magic_spells_accounts_account_id"); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("DysonNetwork.Pass.Account.Notification", b => + { + b.HasOne("DysonNetwork.Pass.Account.Account", "Account") + .WithMany() + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_notifications_accounts_account_id"); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("DysonNetwork.Pass.Account.NotificationPushSubscription", b => + { + b.HasOne("DysonNetwork.Pass.Account.Account", "Account") + .WithMany() + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_notification_push_subscriptions_accounts_account_id"); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("DysonNetwork.Pass.Account.Relationship", b => + { + b.HasOne("DysonNetwork.Pass.Account.Account", "Account") + .WithMany("OutgoingRelationships") + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_account_relationships_accounts_account_id"); + + b.HasOne("DysonNetwork.Pass.Account.Account", "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.Pass.Account.Status", b => + { + b.HasOne("DysonNetwork.Pass.Account.Account", "Account") + .WithMany() + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_account_statuses_accounts_account_id"); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("DysonNetwork.Pass.Auth.AuthChallenge", b => + { + b.HasOne("DysonNetwork.Pass.Account.Account", "Account") + .WithMany("Challenges") + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_auth_challenges_accounts_account_id"); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("DysonNetwork.Pass.Auth.AuthSession", b => + { + b.HasOne("DysonNetwork.Pass.Account.Account", "Account") + .WithMany("Sessions") + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_auth_sessions_accounts_account_id"); + + b.HasOne("DysonNetwork.Pass.Developer.CustomApp", "App") + .WithMany() + .HasForeignKey("AppId") + .HasConstraintName("fk_auth_sessions_custom_apps_app_id"); + + b.HasOne("DysonNetwork.Pass.Auth.AuthChallenge", "Challenge") + .WithMany() + .HasForeignKey("ChallengeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_auth_sessions_auth_challenges_challenge_id"); + + b.Navigation("Account"); + + b.Navigation("App"); + + b.Navigation("Challenge"); + }); + + modelBuilder.Entity("DysonNetwork.Pass.Developer.CustomAppSecret", b => + { + b.HasOne("DysonNetwork.Pass.Developer.CustomApp", "App") + .WithMany("Secrets") + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_custom_app_secrets_custom_apps_app_id"); + + b.Navigation("App"); + }); + + modelBuilder.Entity("DysonNetwork.Pass.Permission.PermissionGroupMember", b => + { + b.HasOne("DysonNetwork.Pass.Permission.PermissionGroup", "Group") + .WithMany("Members") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_permission_group_members_permission_groups_group_id"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("DysonNetwork.Pass.Permission.PermissionNode", b => + { + b.HasOne("DysonNetwork.Pass.Permission.PermissionGroup", "Group") + .WithMany("Nodes") + .HasForeignKey("GroupId") + .HasConstraintName("fk_permission_nodes_permission_groups_group_id"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("DysonNetwork.Pass.Wallet.Order", b => + { + b.HasOne("DysonNetwork.Pass.Wallet.Wallet", "PayeeWallet") + .WithMany() + .HasForeignKey("PayeeWalletId") + .HasConstraintName("fk_payment_orders_wallets_payee_wallet_id"); + + b.HasOne("DysonNetwork.Pass.Wallet.Transaction", "Transaction") + .WithMany() + .HasForeignKey("TransactionId") + .HasConstraintName("fk_payment_orders_payment_transactions_transaction_id"); + + b.Navigation("PayeeWallet"); + + b.Navigation("Transaction"); + }); + + modelBuilder.Entity("DysonNetwork.Pass.Wallet.Subscription", b => + { + b.HasOne("DysonNetwork.Pass.Account.Account", "Account") + .WithMany() + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_wallet_subscriptions_accounts_account_id"); + + b.HasOne("DysonNetwork.Pass.Wallet.Coupon", "Coupon") + .WithMany() + .HasForeignKey("CouponId") + .HasConstraintName("fk_wallet_subscriptions_wallet_coupons_coupon_id"); + + b.Navigation("Account"); + + b.Navigation("Coupon"); + }); + + modelBuilder.Entity("DysonNetwork.Pass.Wallet.Transaction", b => + { + b.HasOne("DysonNetwork.Pass.Wallet.Wallet", "PayeeWallet") + .WithMany() + .HasForeignKey("PayeeWalletId") + .HasConstraintName("fk_payment_transactions_wallets_payee_wallet_id"); + + b.HasOne("DysonNetwork.Pass.Wallet.Wallet", "PayerWallet") + .WithMany() + .HasForeignKey("PayerWalletId") + .HasConstraintName("fk_payment_transactions_wallets_payer_wallet_id"); + + b.Navigation("PayeeWallet"); + + b.Navigation("PayerWallet"); + }); + + modelBuilder.Entity("DysonNetwork.Pass.Wallet.Wallet", b => + { + b.HasOne("DysonNetwork.Pass.Account.Account", "Account") + .WithMany() + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_wallets_accounts_account_id"); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("DysonNetwork.Pass.Wallet.WalletPocket", b => + { + b.HasOne("DysonNetwork.Pass.Wallet.Wallet", "Wallet") + .WithMany("Pockets") + .HasForeignKey("WalletId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_wallet_pockets_wallets_wallet_id"); + + b.Navigation("Wallet"); + }); + + modelBuilder.Entity("DysonNetwork.Pass.Account.Account", 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.Pass.Developer.CustomApp", b => + { + b.Navigation("Secrets"); + }); + + modelBuilder.Entity("DysonNetwork.Pass.Permission.PermissionGroup", b => + { + b.Navigation("Members"); + + b.Navigation("Nodes"); + }); + + modelBuilder.Entity("DysonNetwork.Pass.Wallet.Wallet", b => + { + b.Navigation("Pockets"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/DysonNetwork.Pass/Migrations/20250715075623_ReinitalMigration.cs b/DysonNetwork.Pass/Migrations/20250715075623_ReinitalMigration.cs new file mode 100644 index 0000000..b649b94 --- /dev/null +++ b/DysonNetwork.Pass/Migrations/20250715075623_ReinitalMigration.cs @@ -0,0 +1,40 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace DysonNetwork.Pass.Migrations +{ + /// + public partial class ReinitalMigration : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "background_id", + table: "account_profiles"); + + migrationBuilder.DropColumn( + name: "picture_id", + table: "account_profiles"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "background_id", + table: "account_profiles", + type: "character varying(32)", + maxLength: 32, + nullable: true); + + migrationBuilder.AddColumn( + name: "picture_id", + table: "account_profiles", + type: "character varying(32)", + maxLength: 32, + nullable: true); + } + } +} diff --git a/DysonNetwork.Pass/Migrations/AppDatabaseModelSnapshot.cs b/DysonNetwork.Pass/Migrations/AppDatabaseModelSnapshot.cs index cfd2d9d..1dae7ff 100644 --- a/DysonNetwork.Pass/Migrations/AppDatabaseModelSnapshot.cs +++ b/DysonNetwork.Pass/Migrations/AppDatabaseModelSnapshot.cs @@ -392,11 +392,6 @@ namespace DysonNetwork.Pass.Migrations .HasColumnType("jsonb") .HasColumnName("background"); - b.Property("BackgroundId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("background_id"); - b.Property("Bio") .HasMaxLength(4096) .HasColumnType("character varying(4096)") @@ -451,11 +446,6 @@ namespace DysonNetwork.Pass.Migrations .HasColumnType("jsonb") .HasColumnName("picture"); - b.Property("PictureId") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("picture_id"); - b.Property("Pronouns") .HasMaxLength(1024) .HasColumnType("character varying(1024)") diff --git a/DysonNetwork.Pass/Program.cs b/DysonNetwork.Pass/Program.cs index 56c449e..e1cec59 100644 --- a/DysonNetwork.Pass/Program.cs +++ b/DysonNetwork.Pass/Program.cs @@ -1,5 +1,4 @@ using DysonNetwork.Pass; -using DysonNetwork.Pass.Account; using DysonNetwork.Pass.Startup; using DysonNetwork.Shared.Http; using DysonNetwork.Shared.Registry; diff --git a/DysonNetwork.Sphere/Safety/AbuseReportController.cs b/DysonNetwork.Pass/Safety/AbuseReportController.cs similarity index 91% rename from DysonNetwork.Sphere/Safety/AbuseReportController.cs rename to DysonNetwork.Pass/Safety/AbuseReportController.cs index fdaba61..d26675a 100644 --- a/DysonNetwork.Sphere/Safety/AbuseReportController.cs +++ b/DysonNetwork.Pass/Safety/AbuseReportController.cs @@ -1,10 +1,10 @@ using System.ComponentModel.DataAnnotations; -using DysonNetwork.Sphere.Account; -using DysonNetwork.Sphere.Permission; +using DysonNetwork.Pass.Account; +using DysonNetwork.Pass.Permission; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -namespace DysonNetwork.Sphere.Safety; +namespace DysonNetwork.Pass.Safety; [ApiController] [Route("/api/safety/reports")] @@ -30,7 +30,7 @@ public class AbuseReportController( [ProducesResponseType(StatusCodes.Status400BadRequest)] public async Task> CreateReport([FromBody] CreateReportRequest request) { - if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); + if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); try { @@ -75,7 +75,7 @@ public class AbuseReportController( [FromQuery] bool includeResolved = false ) { - if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); + if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); var totalCount = await safety.CountUserReports(currentUser.Id, includeResolved); var reports = await safety.GetUserReports(currentUser.Id, offset, take, includeResolved); @@ -101,7 +101,7 @@ public class AbuseReportController( [ProducesResponseType(StatusCodes.Status403Forbidden)] public async Task> GetMyReportById(Guid id) { - if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); + if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); var report = await safety.GetReportById(id); if (report == null) return NotFound(); diff --git a/DysonNetwork.Sphere/Safety/SafetyService.cs b/DysonNetwork.Pass/Safety/SafetyService.cs similarity index 97% rename from DysonNetwork.Sphere/Safety/SafetyService.cs rename to DysonNetwork.Pass/Safety/SafetyService.cs index f74a501..c6ee055 100644 --- a/DysonNetwork.Sphere/Safety/SafetyService.cs +++ b/DysonNetwork.Pass/Safety/SafetyService.cs @@ -1,8 +1,8 @@ -using DysonNetwork.Sphere.Account; +using DysonNetwork.Pass.Account; using Microsoft.EntityFrameworkCore; using NodaTime; -namespace DysonNetwork.Sphere.Safety; +namespace DysonNetwork.Pass.Safety; public class SafetyService(AppDatabase db, ILogger logger) { diff --git a/DysonNetwork.Pass/Wallet/PaymentService.cs b/DysonNetwork.Pass/Wallet/PaymentService.cs index abaf064..98d6e50 100644 --- a/DysonNetwork.Pass/Wallet/PaymentService.cs +++ b/DysonNetwork.Pass/Wallet/PaymentService.cs @@ -217,7 +217,7 @@ public class PaymentService( Title = localizer["OrderPaidTitle", $"#{readableOrderId}"], Body = localizer["OrderPaidBody", order.Amount.ToString(CultureInfo.InvariantCulture), order.Currency, readableOrderRemark], - IsSavable = false + IsSavable = true } } ); diff --git a/DysonNetwork.Pass/Wallet/SubscriptionService.cs b/DysonNetwork.Pass/Wallet/SubscriptionService.cs index f6e625e..bd71d7b 100644 --- a/DysonNetwork.Pass/Wallet/SubscriptionService.cs +++ b/DysonNetwork.Pass/Wallet/SubscriptionService.cs @@ -360,7 +360,7 @@ public class SubscriptionService( Topic = "subscriptions.begun", Title = localizer["SubscriptionAppliedTitle", humanReadableName], Body = localizer["SubscriptionAppliedBody", duration, humanReadableName], - IsSavable = false, + IsSavable = true }; notification.Meta.Add("subscription_id", Value.ForString(subscription.Id.ToString())); await pusher.SendPushNotificationToUserAsync( diff --git a/DysonNetwork.Pusher/Program.cs b/DysonNetwork.Pusher/Program.cs index c3e1599..150912c 100644 --- a/DysonNetwork.Pusher/Program.cs +++ b/DysonNetwork.Pusher/Program.cs @@ -15,7 +15,7 @@ builder.Services.AddAppServices(builder.Configuration); builder.Services.AddAppRateLimiting(); builder.Services.AddAppAuthentication(); builder.Services.AddAppSwagger(); -builder.Services.AddDysonAuth(builder.Configuration); +builder.Services.AddDysonAuth(); // Add flush handlers and websocket handlers builder.Services.AddAppFlushHandlers(); diff --git a/DysonNetwork.Shared/Auth/Startup.cs b/DysonNetwork.Shared/Auth/Startup.cs index 7da4185..103aac2 100644 --- a/DysonNetwork.Shared/Auth/Startup.cs +++ b/DysonNetwork.Shared/Auth/Startup.cs @@ -8,8 +8,7 @@ namespace DysonNetwork.Shared.Auth; public static class DysonAuthStartup { public static IServiceCollection AddDysonAuth( - this IServiceCollection services, - IConfiguration configuration + this IServiceCollection services ) { services.AddSingleton(sp => diff --git a/DysonNetwork.Sphere/Chat/ChatRoom.cs b/DysonNetwork.Sphere/Chat/ChatRoom.cs index c37684c..6322029 100644 --- a/DysonNetwork.Sphere/Chat/ChatRoom.cs +++ b/DysonNetwork.Sphere/Chat/ChatRoom.cs @@ -74,7 +74,7 @@ public class ChatMember : ModelBase public Guid ChatRoomId { get; set; } public ChatRoom ChatRoom { get; set; } = null!; public Guid AccountId { get; set; } - public Account Account { get; set; } = null!; + [NotMapped] public Account Account { get; set; } = null!; [MaxLength(1024)] public string? Nick { get; set; } @@ -106,7 +106,7 @@ public class ChatMemberTransmissionObject : ModelBase public Guid Id { get; set; } public Guid ChatRoomId { get; set; } public Guid AccountId { get; set; } - public Account Account { get; set; } = null!; + [NotMapped] public Account Account { get; set; } = null!; [MaxLength(1024)] public string? Nick { get; set; } diff --git a/DysonNetwork.Sphere/Chat/ChatRoomController.cs b/DysonNetwork.Sphere/Chat/ChatRoomController.cs index c5304d9..23535cc 100644 --- a/DysonNetwork.Sphere/Chat/ChatRoomController.cs +++ b/DysonNetwork.Sphere/Chat/ChatRoomController.cs @@ -2,10 +2,10 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using System.ComponentModel.DataAnnotations; using DysonNetwork.Shared; +using DysonNetwork.Shared.Auth; using DysonNetwork.Shared.Data; using DysonNetwork.Shared.Proto; using DysonNetwork.Sphere.Localization; -using DysonNetwork.Sphere.Permission; using DysonNetwork.Sphere.Realm; using Grpc.Core; using Microsoft.AspNetCore.Authorization; @@ -962,7 +962,7 @@ public class ChatRoomController( Topic = "invites.chats", Title = title, Body = body, - IsSavable = false + IsSavable = true } } ); diff --git a/DysonNetwork.Sphere/Chat/ChatRoomController.cs.bak b/DysonNetwork.Sphere/Chat/ChatRoomController.cs.bak deleted file mode 100644 index f5d702c..0000000 --- a/DysonNetwork.Sphere/Chat/ChatRoomController.cs.bak +++ /dev/null @@ -1,947 +0,0 @@ -using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; -using System.ComponentModel.DataAnnotations; -using DysonNetwork.Shared.Data; -using DysonNetwork.Shared.Proto; -using DysonNetwork.Sphere.Localization; -using DysonNetwork.Sphere.Permission; -using DysonNetwork.Sphere.Realm; -using Grpc.Core; -using Microsoft.AspNetCore.Authorization; -using Microsoft.Extensions.Localization; -using NodaTime; - -namespace DysonNetwork.Sphere.Chat; - -[ApiController] -[Route("/api/chat")] -public class ChatRoomController( - AppDatabase db, - ChatRoomService crs, - RealmService rs, - IStringLocalizer localizer, - AccountService.AccountServiceClient accounts, - FileService.FileServiceClient files, - FileReferenceService.FileReferenceServiceClient fileRefs, - ActionLogService.ActionLogServiceClient als -) : ControllerBase -{ - [HttpGet("{id:guid}")] - public async Task> GetChatRoom(Guid id) - { - var chatRoom = await db.ChatRooms - .Where(c => c.Id == id) - .Include(e => e.Realm) - .FirstOrDefaultAsync(); - if (chatRoom is null) return NotFound(); - if (chatRoom.Type != ChatRoomType.DirectMessage) return Ok(chatRoom); - - if (HttpContext.Items["CurrentUser"] is Account currentUser) - chatRoom = await crs.LoadDirectMessageMembers(chatRoom, Guid.Parse(currentUser.Id)); - - return Ok(chatRoom); - } - - [HttpGet] - [Authorize] - public async Task>> ListJoinedChatRooms() - { - if (HttpContext.Items["CurrentUser"] is not Account currentUser) - return Unauthorized(); - var accountId = Guid.Parse(currentUser.Id); - - var chatRooms = await db.ChatMembers - .Where(m => m.AccountId == accountId) - .Where(m => m.JoinedAt != null) - .Where(m => m.LeaveAt == null) - .Include(m => m.ChatRoom) - .Select(m => m.ChatRoom) - .ToListAsync(); - chatRooms = await crs.LoadDirectMessageMembers(chatRooms, accountId); - chatRooms = await crs.SortChatRoomByLastMessage(chatRooms); - - return Ok(chatRooms); - } - - public class DirectMessageRequest - { - [Required] public Guid RelatedUserId { get; set; } - } - - [HttpPost("direct")] - [Authorize] - public async Task> CreateDirectMessage([FromBody] DirectMessageRequest request) - { - if (HttpContext.Items["CurrentUser"] is not Shared.Proto.Account currentUser) - return Unauthorized(); - - var relatedUser = await accounts.GetAccountAsync( - new GetAccountRequest { Id = request.RelatedUserId.ToString() } - ); - if (relatedUser is null) - return BadRequest("Related user was not found"); - - var hasBlocked = await accounts.HasRelationshipAsync(new GetRelationshipRequest() - { - AccountId = currentUser.Id, - RelatedId = request.RelatedUserId.ToString(), - Status = -100 - }); - if (hasBlocked?.Value ?? false) - return StatusCode(403, "You cannot create direct message with a user that blocked you."); - - // Check if DM already exists between these users - var existingDm = await db.ChatRooms - .Include(c => c.Members) - .Where(c => c.Type == ChatRoomType.DirectMessage && c.Members.Count == 2) - .Where(c => c.Members.Any(m => m.AccountId == Guid.Parse(currentUser.Id))) - .Where(c => c.Members.Any(m => m.AccountId == request.RelatedUserId)) - .FirstOrDefaultAsync(); - - if (existingDm != null) - return BadRequest("You already have a DM with this user."); - - // Create new DM chat room - var dmRoom = new ChatRoom - { - Type = ChatRoomType.DirectMessage, - IsPublic = false, - Members = new List - { - new() - { - AccountId = Guid.Parse(currentUser.Id), - Role = ChatMemberRole.Owner, - JoinedAt = Instant.FromDateTimeUtc(DateTime.UtcNow) - }, - new() - { - AccountId = request.RelatedUserId, - Role = ChatMemberRole.Member, - JoinedAt = null, // Pending status - } - } - }; - - db.ChatRooms.Add(dmRoom); - await db.SaveChangesAsync(); - - _ = als.CreateActionLogAsync(new CreateActionLogRequest - { - Action = "chatrooms.create", - Meta = { { "chatroom_id", Google.Protobuf.WellKnownTypes.Value.ForString(dmRoom.Id.ToString()) } }, - AccountId = currentUser.Id, - UserAgent = Request.Headers.UserAgent, - IpAddress = Request.HttpContext.Connection.RemoteIpAddress?.ToString() - }); - - var invitedMember = dmRoom.Members.First(m => m.AccountId == request.RelatedUserId); - invitedMember.ChatRoom = dmRoom; - await _SendInviteNotify(invitedMember, currentUser); - - return Ok(dmRoom); - } - - [HttpGet("direct/{accountId:guid}")] - [Authorize] - public async Task> GetDirectChatRoom(Guid accountId) - { - if (HttpContext.Items["CurrentUser"] is not Shared.Proto.Account currentUser) - return Unauthorized(); - - var room = await db.ChatRooms - .Include(c => c.Members) - .Where(c => c.Type == ChatRoomType.DirectMessage && c.Members.Count == 2) - .Where(c => c.Members.Any(m => m.AccountId == Guid.Parse(currentUser.Id))) - .Where(c => c.Members.Any(m => m.AccountId == accountId)) - .FirstOrDefaultAsync(); - if (room is null) return NotFound(); - - return Ok(room); - } - - public class ChatRoomRequest - { - [Required] [MaxLength(1024)] public string? Name { get; set; } - [MaxLength(4096)] public string? Description { get; set; } - [MaxLength(32)] public string? PictureId { get; set; } - [MaxLength(32)] public string? BackgroundId { get; set; } - public Guid? RealmId { get; set; } - public bool? IsCommunity { get; set; } - public bool? IsPublic { get; set; } - } - - [HttpPost] - [Authorize] - [RequiredPermission("global", "chat.create")] - public async Task> CreateChatRoom(ChatRoomRequest request) - { - if (HttpContext.Items["CurrentUser"] is not Shared.Proto.Account currentUser) return Unauthorized(); - if (request.Name is null) return BadRequest("You cannot create a chat room without a name."); - - var chatRoom = new ChatRoom - { - Name = request.Name, - Description = request.Description ?? string.Empty, - IsCommunity = request.IsCommunity ?? false, - IsPublic = request.IsPublic ?? false, - Type = ChatRoomType.Group, - Members = new List - { - new() - { - Role = ChatMemberRole.Owner, - AccountId = Guid.Parse(currentUser.Id), - JoinedAt = NodaTime.Instant.FromDateTimeUtc(DateTime.UtcNow) - } - } - }; - - if (request.RealmId is not null) - { - if (!await rs.IsMemberWithRole(request.RealmId.Value, Guid.Parse(currentUser.Id), - RealmMemberRole.Moderator)) - return StatusCode(403, "You need at least be a moderator to create chat linked to the realm."); - chatRoom.RealmId = request.RealmId; - } - - if (request.PictureId is not null) - { - try - { - var fileResponse = await files.GetFileAsync(new GetFileRequest { Id = request.PictureId }); - if (fileResponse == null) return BadRequest("Invalid picture id, unable to find the file on cloud."); - chatRoom.Picture = CloudFileReferenceObject.FromProtoValue(fileResponse); - - await fileRefs.CreateReferenceAsync(new CreateReferenceRequest - { - FileId = fileResponse.Id, - Usage = "chatroom.picture", - ResourceId = chatRoom.ResourceIdentifier, - }); - } - catch (RpcException ex) when (ex.StatusCode == Grpc.Core.StatusCode.NotFound) - { - return BadRequest("Invalid picture id, unable to find the file on cloud."); - } - } - - if (request.BackgroundId is not null) - { - try - { - var fileResponse = await files.GetFileAsync(new GetFileRequest { Id = request.BackgroundId }); - if (fileResponse == null) return BadRequest("Invalid background id, unable to find the file on cloud."); - chatRoom.Background = CloudFileReferenceObject.FromProtoValue(fileResponse); - - await fileRefs.CreateReferenceAsync(new CreateReferenceRequest - { - FileId = fileResponse.Id, - Usage = "chatroom.background", - ResourceId = chatRoom.ResourceIdentifier, - }); - } - catch (RpcException ex) when (ex.StatusCode == Grpc.Core.StatusCode.NotFound) - { - return BadRequest("Invalid background id, unable to find the file on cloud."); - } - } - - db.ChatRooms.Add(chatRoom); - await db.SaveChangesAsync(); - - var chatRoomResourceId = $"chatroom:{chatRoom.Id}"; - - if (chatRoom.Picture is not null) - { - await fileRefs.CreateReferenceAsync(new CreateReferenceRequest - { - FileId = chatRoom.Picture.Id, - Usage = "chat.room.picture", - ResourceId = chatRoomResourceId - }); - } - - if (chatRoom.Background is not null) - { - await fileRefs.CreateReferenceAsync(new CreateReferenceRequest - { - FileId = chatRoom.Background.Id, - Usage = "chat.room.background", - ResourceId = chatRoomResourceId - }); - } - - _ = als.CreateActionLogAsync(new CreateActionLogRequest - { - Action = "chatrooms.create", - Meta = { { "chatroom_id", Google.Protobuf.WellKnownTypes.Value.ForString(chatRoom.Id.ToString()) } }, - AccountId = currentUser.Id, - UserAgent = Request.Headers.UserAgent, - IpAddress = Request.HttpContext.Connection.RemoteIpAddress?.ToString() - }); - - return Ok(chatRoom); - } - - - [HttpPatch("{id:guid}")] - public async Task> UpdateChatRoom(Guid id, [FromBody] ChatRoomRequest request) - { - if (HttpContext.Items["CurrentUser"] is not Shared.Proto.Account currentUser) return Unauthorized(); - - var chatRoom = await db.ChatRooms - .Where(e => e.Id == id) - .FirstOrDefaultAsync(); - if (chatRoom is null) return NotFound(); - - if (chatRoom.RealmId is not null) - { - if (!await rs.IsMemberWithRole(chatRoom.RealmId.Value, Guid.Parse(currentUser.Id), - RealmMemberRole.Moderator)) - return StatusCode(403, "You need at least be a realm moderator to update the chat."); - } - else if (!await crs.IsMemberWithRole(chatRoom.Id, Guid.Parse(currentUser.Id), ChatMemberRole.Moderator)) - return StatusCode(403, "You need at least be a moderator to update the chat."); - - if (request.RealmId is not null) - { - var member = await db.RealmMembers - .Where(m => m.AccountId == Guid.Parse(currentUser.Id)) - .Where(m => m.RealmId == request.RealmId) - .FirstOrDefaultAsync(); - if (member is null || member.Role < RealmMemberRole.Moderator) - return StatusCode(403, "You need at least be a moderator to transfer the chat linked to the realm."); - chatRoom.RealmId = member.RealmId; - } - - if (request.PictureId is not null) - { - try - { - var fileResponse = await files.GetFileAsync(new GetFileRequest { Id = request.PictureId }); - if (fileResponse == null) return BadRequest("Invalid picture id, unable to find the file on cloud."); - - // Remove old references for pictures - await fileRefs.DeleteResourceReferencesAsync(new DeleteResourceReferencesRequest - { - ResourceId = chatRoom.ResourceIdentifier, - Usage = "chat.room.picture" - }); - - // Add a new reference - await fileRefs.CreateReferenceAsync(new CreateReferenceRequest - { - FileId = fileResponse.Id, - Usage = "chat.room.picture", - ResourceId = chatRoom.ResourceIdentifier - }); - - chatRoom.Picture = CloudFileReferenceObject.FromProtoValue(fileResponse); - } - catch (RpcException ex) when (ex.StatusCode == Grpc.Core.StatusCode.NotFound) - { - return BadRequest("Invalid picture id, unable to find the file on cloud."); - } - } - - if (request.BackgroundId is not null) - { - try - { - var fileResponse = await files.GetFileAsync(new GetFileRequest { Id = request.BackgroundId }); - if (fileResponse == null) return BadRequest("Invalid background id, unable to find the file on cloud."); - - // Remove old references for backgrounds - await fileRefs.DeleteResourceReferencesAsync(new DeleteResourceReferencesRequest - { - ResourceId = chatRoom.ResourceIdentifier, - Usage = "chat.room.background" - }); - - // Add a new reference - await fileRefs.CreateReferenceAsync(new CreateReferenceRequest - { - FileId = fileResponse.Id, - Usage = "chat.room.background", - ResourceId = chatRoom.ResourceIdentifier - }); - - chatRoom.Background = CloudFileReferenceObject.FromProtoValue(fileResponse); - } - catch (RpcException ex) when (ex.StatusCode == Grpc.Core.StatusCode.NotFound) - { - return BadRequest("Invalid background id, unable to find the file on cloud."); - } - } - - if (request.Name is not null) - chatRoom.Name = request.Name; - if (request.Description is not null) - chatRoom.Description = request.Description; - if (request.IsCommunity is not null) - chatRoom.IsCommunity = request.IsCommunity.Value; - if (request.IsPublic is not null) - chatRoom.IsPublic = request.IsPublic.Value; - - db.ChatRooms.Update(chatRoom); - await db.SaveChangesAsync(); - - _ = als.CreateActionLogAsync(new CreateActionLogRequest - { - Action = "chatrooms.update", - Meta = { { "chatroom_id", Google.Protobuf.WellKnownTypes.Value.ForString(chatRoom.Id.ToString()) } }, - AccountId = currentUser.Id, - UserAgent = Request.Headers.UserAgent, - IpAddress = Request.HttpContext.Connection.RemoteIpAddress?.ToString() - }); - - return Ok(chatRoom); - } - - [HttpDelete("{id:guid}")] - public async Task DeleteChatRoom(Guid id) - { - if (HttpContext.Items["CurrentUser"] is not Shared.Proto.Account currentUser) return Unauthorized(); - - var chatRoom = await db.ChatRooms - .Where(e => e.Id == id) - .FirstOrDefaultAsync(); - if (chatRoom is null) return NotFound(); - - if (chatRoom.RealmId is not null) - { - if (!await rs.IsMemberWithRole(chatRoom.RealmId.Value, Guid.Parse(currentUser.Id), - RealmMemberRole.Moderator)) - return StatusCode(403, "You need at least be a realm moderator to delete the chat."); - } - else if (!await crs.IsMemberWithRole(chatRoom.Id, Guid.Parse(currentUser.Id), ChatMemberRole.Owner)) - return StatusCode(403, "You need at least be the owner to delete the chat."); - - var chatRoomResourceId = $"chatroom:{chatRoom.Id}"; - - // Delete all file references for this chat room - await fileRefs.DeleteResourceReferencesAsync(new DeleteResourceReferencesRequest - { - ResourceId = chatRoomResourceId - }); - - db.ChatRooms.Remove(chatRoom); - await db.SaveChangesAsync(); - - _ = als.CreateActionLogAsync(new CreateActionLogRequest - { - Action = "chatrooms.delete", - Meta = { { "chatroom_id", Google.Protobuf.WellKnownTypes.Value.ForString(chatRoom.Id.ToString()) } }, - AccountId = currentUser.Id, - UserAgent = Request.Headers.UserAgent, - IpAddress = Request.HttpContext.Connection.RemoteIpAddress?.ToString() - }); - - return NoContent(); - } - - [HttpGet("{roomId:guid}/members/me")] - [Authorize] - public async Task> GetRoomIdentity(Guid roomId) - { - if (HttpContext.Items["CurrentUser"] is not Shared.Proto.Account currentUser) - return Unauthorized(); - - var member = await db.ChatMembers - .Where(m => m.AccountId == Guid.Parse(currentUser.Id) && m.ChatRoomId == roomId) - .Include(m => m.Account) - .Include(m => m.Account.Profile) - .FirstOrDefaultAsync(); - - if (member == null) - return NotFound(); - - return Ok(member); - } - - [HttpGet("{roomId:guid}/members")] - public async Task>> ListMembers(Guid roomId, [FromQuery] int take = 20, - [FromQuery] int skip = 0, [FromQuery] bool withStatus = false, [FromQuery] string? status = null) - { - var currentUser = HttpContext.Items["CurrentUser"] as Shared.Proto.Account; - - var room = await db.ChatRooms - .FirstOrDefaultAsync(r => r.Id == roomId); - if (room is null) return NotFound(); - - if (!room.IsPublic) - { - if (currentUser is null) return Unauthorized(); - var member = await db.ChatMembers - .FirstOrDefaultAsync(m => m.ChatRoomId == roomId && m.AccountId == Guid.Parse(currentUser.Id)); - if (member is null) return StatusCode(403, "You need to be a member to see members of private chat room."); - } - - IQueryable query = db.ChatMembers - .Where(m => m.ChatRoomId == roomId) - .Where(m => m.LeaveAt == null) // Add this condition to exclude left members - .Include(m => m.Account) - .Include(m => m.Account.Profile); - - // if (withStatus) - // { - // var members = await query - // .OrderBy(m => m.JoinedAt) - // .ToListAsync(); - // - // var memberStatuses = await aes.GetStatuses(members.Select(m => m.AccountId).ToList()); - // - // if (!string.IsNullOrEmpty(status)) - // { - // members = members.Where(m => - // memberStatuses.TryGetValue(m.AccountId, out var s) && s.Label != null && - // s.Label.Equals(status, StringComparison.OrdinalIgnoreCase)).ToList(); - // } - // - // members = members.OrderByDescending(m => memberStatuses.TryGetValue(m.AccountId, out var s) && s.IsOnline) - // .ToList(); - // - // var total = members.Count; - // Response.Headers.Append("X-Total", total.ToString()); - // - // var result = members.Skip(skip).Take(take).ToList(); - // - // return Ok(result); - // } - // else - // { - var total = await query.CountAsync(); - Response.Headers.Append("X-Total", total.ToString()); - - var members = await query - .OrderBy(m => m.JoinedAt) - .Skip(skip) - .Take(take) - .ToListAsync(); - - return Ok(members); - // } - } - - - public class ChatMemberRequest - { - [Required] public Guid RelatedUserId { get; set; } - [Required] public int Role { get; set; } - } - - [HttpPost("invites/{roomId:guid}")] - [Authorize] - public async Task> InviteMember(Guid roomId, - [FromBody] ChatMemberRequest request) - { - if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); - var accountId = Guid.Parse(currentUser.Id); - - // Get related user account - var relatedUser = await accounts.GetAccountAsync(new GetAccountRequest { Id = request.RelatedUserId.ToString() }); - if (relatedUser == null) return BadRequest("Related user was not found"); - - // Check if the user has blocked the current user - var relationship = await accounts.GetRelationshipAsync(new GetRelationshipRequest - { - AccountId = currentUser.Id, - RelatedId = relatedUser.Id, - Status = -100 - }); - - if (relationship != null && relationship.Relationship.Status == -100) - return StatusCode(403, "You cannot invite a user that blocked you."); - - var chatRoom = await db.ChatRooms - .Where(p => p.Id == roomId) - .FirstOrDefaultAsync(); - if (chatRoom is null) return NotFound(); - - // Handle realm-owned chat rooms - if (chatRoom.RealmId is not null) - { - var realmMember = await db.RealmMembers - .Where(m => m.AccountId == accountId) - .Where(m => m.RealmId == chatRoom.RealmId) - .FirstOrDefaultAsync(); - if (realmMember is null || realmMember.Role < RealmMemberRole.Moderator) - return StatusCode(403, "You need at least be a realm moderator to invite members to this chat."); - } - else - { - var chatMember = await db.ChatMembers - .Where(m => m.AccountId == accountId) - .Where(m => m.ChatRoomId == roomId) - .FirstOrDefaultAsync(); - if (chatMember is null) return StatusCode(403, "You are not even a member of the targeted chat room."); - if (chatMember.Role < ChatMemberRole.Moderator) - return StatusCode(403, - "You need at least be a moderator to invite other members to this chat room."); - if (chatMember.Role < request.Role) - return StatusCode(403, "You cannot invite member with higher permission than yours."); - } - - var hasExistingMember = await db.ChatMembers - .Where(m => m.AccountId == request.RelatedUserId) - .Where(m => m.ChatRoomId == roomId) - .Where(m => m.LeaveAt == null) - .AnyAsync(); - if (hasExistingMember) - return BadRequest("This user has been joined the chat cannot be invited again."); - - var newMember = new ChatMember - { - AccountId = Guid.Parse(relatedUser.Id), - ChatRoomId = roomId, - Role = request.Role, - }; - - db.ChatMembers.Add(newMember); - await db.SaveChangesAsync(); - - newMember.ChatRoom = chatRoom; - await _SendInviteNotify(newMember, currentUser); - - _ = als.CreateActionLogAsync(new CreateActionLogRequest - { - Action = "chatrooms.invite", - Meta = - { - { "chatroom_id", Google.Protobuf.WellKnownTypes.Value.ForString(chatRoom.Id.ToString()) }, - { "account_id", Google.Protobuf.WellKnownTypes.Value.ForString(relatedUser.Id.ToString()) } - }, - AccountId = currentUser.Id, - UserAgent = Request.Headers.UserAgent, - IpAddress = Request.HttpContext.Connection.RemoteIpAddress?.ToString() - }); - - return Ok(newMember); - } - - [HttpGet("invites")] - [Authorize] - public async Task>> ListChatInvites() - { - if (HttpContext.Items["CurrentUser"] is not Shared.Proto.Account currentUser) return Unauthorized(); - var accountId = Guid.Parse(currentUser.Id); - - var members = await db.ChatMembers - .Where(m => m.AccountId == accountId) - .Where(m => m.JoinedAt == null) - .Include(e => e.ChatRoom) - .Include(e => e.Account) - .Include(e => e.Account.Profile) - .ToListAsync(); - - var chatRooms = members.Select(m => m.ChatRoom).ToList(); - var directMembers = - (await crs.LoadDirectMessageMembers(chatRooms, accountId)).ToDictionary(c => c.Id, c => c.Members); - - foreach (var member in members.Where(member => member.ChatRoom.Type == ChatRoomType.DirectMessage)) - member.ChatRoom.Members = directMembers[member.ChatRoom.Id]; - - return members.ToList(); - } - - [HttpPost("invites/{roomId:guid}/accept")] - [Authorize] - public async Task> AcceptChatInvite(Guid roomId) - { - if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); - var accountId = Guid.Parse(currentUser.Id); - - var member = await db.ChatMembers - .Where(m => m.AccountId == accountId) - .Where(m => m.ChatRoomId == roomId) - .Where(m => m.JoinedAt == null) - .FirstOrDefaultAsync(); - if (member is null) return NotFound(); - - member.JoinedAt = Instant.FromDateTimeUtc(DateTime.UtcNow); - db.Update(member); - await db.SaveChangesAsync(); - _ = crs.PurgeRoomMembersCache(roomId); - - als.CreateActionLogFromRequest( - ActionLogType.ChatroomJoin, - new Dictionary { { "chatroom_id", roomId } }, Request - ); - - return Ok(member); - } - - [HttpPost("invites/{roomId:guid}/decline")] - [Authorize] - public async Task DeclineChatInvite(Guid roomId) - { - if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); - var accountId = Guid.Parse(currentUser.Id); - - var member = await db.ChatMembers - .Where(m => m.AccountId == accountId) - .Where(m => m.ChatRoomId == roomId) - .Where(m => m.JoinedAt == null) - .FirstOrDefaultAsync(); - if (member is null) return NotFound(); - - member.LeaveAt = SystemClock.Instance.GetCurrentInstant(); - await db.SaveChangesAsync(); - - return NoContent(); - } - - public class ChatMemberNotifyRequest - { - public ChatMemberNotify? NotifyLevel { get; set; } - public Instant? BreakUntil { get; set; } - } - - [HttpPatch("{roomId:guid}/members/me/notify")] - [Authorize] - public async Task> UpdateChatMemberNotify( - Guid roomId, - [FromBody] ChatMemberNotifyRequest request - ) - { - if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); - - var chatRoom = await db.ChatRooms - .Where(r => r.Id == roomId) - .FirstOrDefaultAsync(); - if (chatRoom is null) return NotFound(); - - var accountId = Guid.Parse(currentUser.Id); - var targetMember = await db.ChatMembers - .Where(m => m.AccountId == accountId && m.ChatRoomId == roomId) - .FirstOrDefaultAsync(); - if (targetMember is null) return BadRequest("You have not joined this chat room."); - if (request.NotifyLevel is not null) - targetMember.Notify = request.NotifyLevel.Value; - if (request.BreakUntil is not null) - targetMember.BreakUntil = request.BreakUntil.Value; - - db.ChatMembers.Update(targetMember); - await db.SaveChangesAsync(); - - await crs.PurgeRoomMembersCache(roomId); - - return Ok(targetMember); - } - - [HttpPatch("{roomId:guid}/members/{memberId:guid}/role")] - [Authorize] - public async Task> UpdateChatMemberRole(Guid roomId, Guid memberId, [FromBody] int newRole) - { - if (newRole >= ChatMemberRole.Owner) return BadRequest("Unable to set chat member to owner or greater role."); - if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); - - var chatRoom = await db.ChatRooms - .Where(r => r.Id == roomId) - .FirstOrDefaultAsync(); - if (chatRoom is null) return NotFound(); - - // Check if the chat room is owned by a realm - if (chatRoom.RealmId is not null) - { - var realmMember = await db.RealmMembers - .Where(m => m.AccountId == Guid.Parse(currentUser.Id)) - .Where(m => m.RealmId == chatRoom.RealmId) - .FirstOrDefaultAsync(); - if (realmMember is null || realmMember.Role < RealmMemberRole.Moderator) - return StatusCode(403, "You need at least be a realm moderator to change member roles."); - } - else - { - var targetMember = await db.ChatMembers - .Where(m => m.AccountId == memberId && m.ChatRoomId == roomId) - .FirstOrDefaultAsync(); - if (targetMember is null) return NotFound(); - - // Check if the current user has permission to change roles - if ( - !await crs.IsMemberWithRole( - chatRoom.Id, - Guid.Parse(currentUser.Id), - ChatMemberRole.Moderator, - targetMember.Role, - newRole - ) - ) - return StatusCode(403, "You don't have enough permission to edit the roles of members."); - - targetMember.Role = newRole; - db.ChatMembers.Update(targetMember); - await db.SaveChangesAsync(); - - await crs.PurgeRoomMembersCache(roomId); - - _ = als.CreateActionLogAsync(new CreateActionLogRequest - { - Action = "chatrooms.role.edit", - Meta = - { - { "chatroom_id", Google.Protobuf.WellKnownTypes.Value.ForString(roomId.ToString()) }, - { "account_id", Google.Protobuf.WellKnownTypes.Value.ForString(memberId.ToString()) }, - { "new_role", Google.Protobuf.WellKnownTypes.Value.ForNumber(newRole) } - }, - AccountId = currentUser.Id, - UserAgent = Request.Headers.UserAgent, - IpAddress = Request.HttpContext.Connection.RemoteIpAddress?.ToString() - }); - - return Ok(targetMember); - } - - return BadRequest(); - } - - [HttpDelete("{roomId:guid}/members/{memberId:guid}")] - [Authorize] - public async Task RemoveChatMember(Guid roomId, Guid memberId) - { - if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); - - var chatRoom = await db.ChatRooms - .Where(r => r.Id == roomId) - .FirstOrDefaultAsync(); - if (chatRoom is null) return NotFound(); - - // Check if the chat room is owned by a realm - if (chatRoom.RealmId is not null) - { - if (!await rs.IsMemberWithRole(chatRoom.RealmId.Value, Guid.Parse(currentUser.Id), - RealmMemberRole.Moderator)) - return StatusCode(403, "You need at least be a realm moderator to remove members."); - } - else - { - if (!await crs.IsMemberWithRole(chatRoom.Id, Guid.Parse(currentUser.Id), ChatMemberRole.Moderator)) - return StatusCode(403, "You need at least be a moderator to remove members."); - - // Find the target member - var member = await db.ChatMembers - .Where(m => m.AccountId == memberId && m.ChatRoomId == roomId) - .FirstOrDefaultAsync(); - if (member is null) return NotFound(); - - // Check if the current user has sufficient permissions - if (!await crs.IsMemberWithRole(chatRoom.Id, memberId, member.Role)) - return StatusCode(403, "You cannot remove members with equal or higher roles."); - - member.LeaveAt = SystemClock.Instance.GetCurrentInstant(); - await db.SaveChangesAsync(); - _ = crs.PurgeRoomMembersCache(roomId); - - _ = als.CreateActionLogAsync(new CreateActionLogRequest - { - Action = "chatrooms.kick", - Meta = - { - { "chatroom_id", Google.Protobuf.WellKnownTypes.Value.ForString(roomId.ToString()) }, - { "account_id", Google.Protobuf.WellKnownTypes.Value.ForString(memberId.ToString()) } - }, - AccountId = currentUser.Id, - UserAgent = Request.Headers.UserAgent, - IpAddress = Request.HttpContext.Connection.RemoteIpAddress?.ToString() - }); - - return NoContent(); - } - - return BadRequest(); - } - - - [HttpPost("{roomId:guid}/members/me")] - [Authorize] - public async Task> JoinChatRoom(Guid roomId) - { - if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); - - var chatRoom = await db.ChatRooms - .Where(r => r.Id == roomId) - .FirstOrDefaultAsync(); - if (chatRoom is null) return NotFound(); - if (!chatRoom.IsCommunity) - return StatusCode(403, "This chat room isn't a community. You need an invitation to join."); - - var existingMember = await db.ChatMembers - .FirstOrDefaultAsync(m => m.AccountId == Guid.Parse(currentUser.Id) && m.ChatRoomId == roomId); - if (existingMember != null) - return BadRequest("You are already a member of this chat room."); - - var newMember = new ChatMember - { - AccountId = Guid.Parse(currentUser.Id), - ChatRoomId = roomId, - Role = ChatMemberRole.Member, - JoinedAt = NodaTime.Instant.FromDateTimeUtc(DateTime.UtcNow) - }; - - db.ChatMembers.Add(newMember); - await db.SaveChangesAsync(); - _ = crs.PurgeRoomMembersCache(roomId); - - als.CreateActionLogFromRequest( - ActionLogType.ChatroomJoin, - new Dictionary { { "chatroom_id", roomId } }, Request - ); - - return Ok(chatRoom); - } - - [HttpDelete("{roomId:guid}/members/me")] - [Authorize] - public async Task LeaveChat(Guid roomId) - { - if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); - - var member = await db.ChatMembers - .Where(m => m.AccountId == Guid.Parse(currentUser.Id)) - .Where(m => m.ChatRoomId == roomId) - .FirstOrDefaultAsync(); - if (member is null) return NotFound(); - - if (member.Role == ChatMemberRole.Owner) - { - // Check if this is the only owner - var otherOwners = await db.ChatMembers - .Where(m => m.ChatRoomId == roomId) - .Where(m => m.Role == ChatMemberRole.Owner) - .Where(m => m.AccountId != Guid.Parse(currentUser.Id)) - .AnyAsync(); - - if (!otherOwners) - return BadRequest("The last owner cannot leave the chat. Transfer ownership first or delete the chat."); - } - - member.LeaveAt = Instant.FromDateTimeUtc(DateTime.UtcNow); - await db.SaveChangesAsync(); - await crs.PurgeRoomMembersCache(roomId); - - _ = als.CreateActionLogAsync(new CreateActionLogRequest - { - Action = "chatrooms.leave", - Meta = { { "chatroom_id", Google.Protobuf.WellKnownTypes.Value.ForString(roomId.ToString()) } }, - AccountId = currentUser.Id, - UserAgent = Request.Headers.UserAgent, - IpAddress = Request.HttpContext.Connection.RemoteIpAddress?.ToString() - }); - - return NoContent(); - } - - private async Task _SendInviteNotify(ChatMember member, Account sender) - { - string title = localizer["ChatInviteTitle"]; - - string body = member.ChatRoom.Type == ChatRoomType.DirectMessage - ? localizer["ChatInviteDirectBody", sender.Nick] - : localizer["ChatInviteBody", member.ChatRoom.Name ?? "Unnamed"]; - - AccountService.SetCultureInfo(member.Account); - await nty.SendNotification(member.Account, "invites.chats", title, null, body, actionUri: "/chat"); - } -} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Chat/ChatService.cs b/DysonNetwork.Sphere/Chat/ChatService.cs index af40154..465297e 100644 --- a/DysonNetwork.Sphere/Chat/ChatService.cs +++ b/DysonNetwork.Sphere/Chat/ChatService.cs @@ -233,6 +233,7 @@ public partial class ChatService( Body = !string.IsNullOrEmpty(message.Content) ? message.Content[..Math.Min(message.Content.Length, 100)] : "", + IsSavable = false }; notification.Meta.Add(GrpcTypeHelper.ConvertToValueMap(metaDict)); diff --git a/DysonNetwork.Sphere/Developer/DeveloperController.cs b/DysonNetwork.Sphere/Developer/DeveloperController.cs index 2f251ea..79e43c5 100644 --- a/DysonNetwork.Sphere/Developer/DeveloperController.cs +++ b/DysonNetwork.Sphere/Developer/DeveloperController.cs @@ -1,5 +1,5 @@ +using DysonNetwork.Shared.Auth; using DysonNetwork.Shared.Proto; -using DysonNetwork.Sphere.Permission; using DysonNetwork.Sphere.Publisher; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; diff --git a/DysonNetwork.Sphere/Post/PostController.cs b/DysonNetwork.Sphere/Post/PostController.cs index 3af0f0c..00b5407 100644 --- a/DysonNetwork.Sphere/Post/PostController.cs +++ b/DysonNetwork.Sphere/Post/PostController.cs @@ -1,8 +1,8 @@ using System.ComponentModel.DataAnnotations; +using DysonNetwork.Shared.Auth; using DysonNetwork.Shared.Content; using DysonNetwork.Shared.Data; using DysonNetwork.Shared.Proto; -using DysonNetwork.Sphere.Permission; using DysonNetwork.Sphere.Publisher; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; diff --git a/DysonNetwork.Sphere/Program.cs b/DysonNetwork.Sphere/Program.cs index 452e629..8471b02 100644 --- a/DysonNetwork.Sphere/Program.cs +++ b/DysonNetwork.Sphere/Program.cs @@ -1,3 +1,5 @@ +using DysonNetwork.Shared.Auth; +using DysonNetwork.Shared.Registry; using DysonNetwork.Sphere; using DysonNetwork.Sphere.Startup; using Microsoft.EntityFrameworkCore; @@ -12,13 +14,15 @@ builder.ConfigureAppKestrel(); builder.Services.AddAppMetrics(); // Add application services +builder.Services.AddRegistryService(builder.Configuration); builder.Services.AddAppServices(builder.Configuration); builder.Services.AddAppRateLimiting(); builder.Services.AddAppAuthentication(); builder.Services.AddAppSwagger(); - -// Add file storage -builder.Services.AddAppFileStorage(builder.Configuration); +builder.Services.AddDysonAuth(); +builder.Services.AddAccountService(); +builder.Services.AddPusherService(); +builder.Services.AddDriveService(); // Add flush handlers and websocket handlers builder.Services.AddAppFlushHandlers(); @@ -38,10 +42,7 @@ using (var scope = app.Services.CreateScope()) await db.Database.MigrateAsync(); } -// Get the TusDiskStore instance -var tusDiskStore = app.Services.GetRequiredService(); - // Configure application middleware pipeline -app.ConfigureAppMiddleware(builder.Configuration, tusDiskStore); +app.ConfigureAppMiddleware(builder.Configuration); app.Run(); \ No newline at end of file diff --git a/DysonNetwork.Sphere/Publisher/PublisherSubscriptionService.cs b/DysonNetwork.Sphere/Publisher/PublisherSubscriptionService.cs index 36585ba..517b00a 100644 --- a/DysonNetwork.Sphere/Publisher/PublisherSubscriptionService.cs +++ b/DysonNetwork.Sphere/Publisher/PublisherSubscriptionService.cs @@ -1,7 +1,8 @@ -using DysonNetwork.Sphere.Account; +using DysonNetwork.Shared; +using DysonNetwork.Shared.Cache; +using DysonNetwork.Shared.Proto; using DysonNetwork.Sphere.Localization; using DysonNetwork.Sphere.Post; -using DysonNetwork.Sphere.Storage; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Localization; @@ -9,10 +10,11 @@ namespace DysonNetwork.Sphere.Publisher; public class PublisherSubscriptionService( AppDatabase db, - NotificationService nty, PostService ps, IStringLocalizer localizer, - ICacheService cache + ICacheService cache, + PusherService.PusherServiceClient pusher, + AccountService.AccountServiceClient accounts ) { /// @@ -50,7 +52,6 @@ public class PublisherSubscriptionService( public async Task NotifySubscriberPost(Post.Post post) { var subscribers = await db.PublisherSubscriptions - .Include(p => p.Account) .Where(p => p.PublisherId == post.PublisherId && p.Status == PublisherSubscriptionStatus.Active) .ToListAsync(); @@ -67,23 +68,35 @@ public class PublisherSubscriptionService( { "publisher_id", post.Publisher.Id.ToString() } }; + + var queryRequest = new GetAccountBatchRequest(); + queryRequest.Id.AddRange(subscribers.DistinctBy(s => s.AccountId).Select(m => m.AccountId.ToString())); + var queryResponse = await accounts.GetAccountBatchAsync(queryRequest); + + var notification = new PushNotification + { + Topic = "posts.new", + Title = localizer["PostSubscriptionTitle", post.Publisher.Name, title], + Body = message, + IsSavable = true, + ActionUri = $"/posts/{post.Id}" + }; + notification.Meta.Add(GrpcTypeHelper.ConvertToValueMap(data)); + // Notify each subscriber var notifiedCount = 0; - foreach (var subscription in subscribers.DistinctBy(s => s.AccountId)) + foreach (var target in queryResponse.Accounts) { try { - AccountService.SetCultureInfo(subscription.Account); - await nty.SendNotification( - subscription.Account, - "posts.new", - localizer["PostSubscriptionTitle", post.Publisher.Name, title], - null, - message, - data, - actionUri: $"/posts/{post.Id}" + CultureService.SetCultureInfo(target); + await pusher.SendPushNotificationToUserAsync( + new SendPushNotificationToUserRequest + { + UserId = target.Id, + Notification = notification + } ); - notifiedCount++; } catch (Exception) @@ -117,7 +130,6 @@ public class PublisherSubscriptionService( public async Task> GetPublisherSubscribersAsync(Guid publisherId) { return await db.PublisherSubscriptions - .Include(ps => ps.Account) .Where(ps => ps.PublisherId == publisherId && ps.Status == PublisherSubscriptionStatus.Active) .ToListAsync(); } diff --git a/DysonNetwork.Sphere/Realm/Realm.cs b/DysonNetwork.Sphere/Realm/Realm.cs index fdd5d8c..6b41e8f 100644 --- a/DysonNetwork.Sphere/Realm/Realm.cs +++ b/DysonNetwork.Sphere/Realm/Realm.cs @@ -1,8 +1,8 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using System.Text.Json.Serialization; +using DysonNetwork.Shared.Data; using DysonNetwork.Sphere.Chat; -using DysonNetwork.Sphere.Storage; using Microsoft.EntityFrameworkCore; using NodaTime; @@ -25,7 +25,7 @@ public class Realm : ModelBase, IIdentifiedResource [Column(TypeName = "jsonb")] public CloudFileReferenceObject? Picture { get; set; } [Column(TypeName = "jsonb")] public CloudFileReferenceObject? Background { get; set; } - [Column(TypeName = "jsonb")] public Account.VerificationMark? Verification { get; set; } + [Column(TypeName = "jsonb")] public VerificationMark? Verification { get; set; } [JsonIgnore] public ICollection Members { get; set; } = new List(); [JsonIgnore] public ICollection ChatRooms { get; set; } = new List(); @@ -48,7 +48,6 @@ public class RealmMember : ModelBase public Guid RealmId { get; set; } public Realm Realm { get; set; } = null!; public Guid AccountId { get; set; } - public Account Account { get; set; } = null!; public int Role { get; set; } = RealmMemberRole.Normal; public Instant? JoinedAt { get; set; } diff --git a/DysonNetwork.Sphere/Realm/RealmChatController.cs b/DysonNetwork.Sphere/Realm/RealmChatController.cs index 61567e6..a820972 100644 --- a/DysonNetwork.Sphere/Realm/RealmChatController.cs +++ b/DysonNetwork.Sphere/Realm/RealmChatController.cs @@ -1,3 +1,4 @@ +using DysonNetwork.Shared.Proto; using DysonNetwork.Sphere.Chat; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; @@ -14,6 +15,7 @@ public class RealmChatController(AppDatabase db, RealmService rs) : ControllerBa public async Task>> ListRealmChat(string slug) { var currentUser = HttpContext.Items["CurrentUser"] as Account; + var accountId = currentUser is null ? Guid.Empty : Guid.Parse(currentUser.Id); var realm = await db.Realms .Where(r => r.Slug == slug) @@ -22,7 +24,7 @@ public class RealmChatController(AppDatabase db, RealmService rs) : ControllerBa if (!realm.IsPublic) { if (currentUser is null) return Unauthorized(); - if (!await rs.IsMemberWithRole(realm.Id, currentUser.Id, RealmMemberRole.Normal)) + if (!await rs.IsMemberWithRole(realm.Id, accountId, RealmMemberRole.Normal)) return StatusCode(403, "You need at least one member to view the realm's chat."); } diff --git a/DysonNetwork.Sphere/Realm/RealmController.cs b/DysonNetwork.Sphere/Realm/RealmController.cs index ca73f2b..66debd6 100644 --- a/DysonNetwork.Sphere/Realm/RealmController.cs +++ b/DysonNetwork.Sphere/Realm/RealmController.cs @@ -1,8 +1,11 @@ using System.ComponentModel.DataAnnotations; -using Microsoft.AspNetCore.Mvc; +using DysonNetwork.Shared.Data; +using DysonNetwork.Shared.Proto; using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using NodaTime; +using Google.Protobuf.WellKnownTypes; namespace DysonNetwork.Sphere.Realm; @@ -11,10 +14,10 @@ namespace DysonNetwork.Sphere.Realm; public class RealmController( AppDatabase db, RealmService rs, - FileReferenceService fileRefService, - RelationshipService rels, - ActionLogService als, - AccountEventService aes + FileService.FileServiceClient files, + FileReferenceService.FileReferenceServiceClient fileRefs, + ActionLogService.ActionLogServiceClient als, + AccountService.AccountServiceClient accounts ) : Controller { [HttpGet("{slug}")] @@ -33,10 +36,10 @@ public class RealmController( public async Task>> ListJoinedRealms() { if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); - var userId = currentUser.Id; + var accountId = Guid.Parse(currentUser.Id); var members = await db.RealmMembers - .Where(m => m.AccountId == userId) + .Where(m => m.AccountId == accountId) .Where(m => m.JoinedAt != null) .Where(m => m.LeaveAt == null) .Include(e => e.Realm) @@ -51,10 +54,10 @@ public class RealmController( public async Task>> ListInvites() { if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); - var userId = currentUser.Id; + var accountId = Guid.Parse(currentUser.Id); var members = await db.RealmMembers - .Where(m => m.AccountId == userId) + .Where(m => m.AccountId == accountId) .Where(m => m.JoinedAt == null) .Include(e => e.Realm) .ToListAsync(); @@ -74,12 +77,19 @@ public class RealmController( [FromBody] RealmMemberRequest request) { if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); - var userId = currentUser.Id; + var accountId = Guid.Parse(currentUser.Id); - var relatedUser = await db.Accounts.FindAsync(request.RelatedUserId); - if (relatedUser is null) return BadRequest("Related user was not found"); + var relatedUser = + await accounts.GetAccountAsync(new GetAccountRequest { Id = request.RelatedUserId.ToString() }); + if (relatedUser == null) return BadRequest("Related user was not found"); - if (await rels.HasRelationshipWithStatus(currentUser.Id, relatedUser.Id, RelationshipStatus.Blocked)) + var hasBlocked = await accounts.HasRelationshipAsync(new GetRelationshipRequest() + { + AccountId = currentUser.Id, + RelatedId = request.RelatedUserId.ToString(), + Status = -100 + }); + if (hasBlocked?.Value ?? false) return StatusCode(403, "You cannot invite a user that blocked you."); var realm = await db.Realms @@ -87,11 +97,11 @@ public class RealmController( .FirstOrDefaultAsync(); if (realm is null) return NotFound(); - if (!await rs.IsMemberWithRole(realm.Id, userId, request.Role)) + if (!await rs.IsMemberWithRole(realm.Id, accountId, request.Role)) return StatusCode(403, "You cannot invite member has higher permission than yours."); var hasExistingMember = await db.RealmMembers - .Where(m => m.AccountId == request.RelatedUserId) + .Where(m => m.AccountId == Guid.Parse(relatedUser.Id)) .Where(m => m.RealmId == realm.Id) .Where(m => m.LeaveAt == null) .AnyAsync(); @@ -100,7 +110,7 @@ public class RealmController( var member = new RealmMember { - AccountId = relatedUser.Id, + AccountId = Guid.Parse(relatedUser.Id), RealmId = realm.Id, Role = request.Role, }; @@ -108,12 +118,21 @@ public class RealmController( db.RealmMembers.Add(member); await db.SaveChangesAsync(); - als.CreateActionLogFromRequest( - ActionLogType.RealmInvite, - new Dictionary { { "realm_id", realm.Id }, { "account_id", member.AccountId } }, Request - ); + _ = als.CreateActionLogAsync(new CreateActionLogRequest + { + Action = "realms.members.invite", + Meta = + { + { "realm_id", Value.ForString(realm.Id.ToString()) }, + { "account_id", Value.ForString(member.AccountId.ToString()) }, + { "role", Value.ForNumber(request.Role) } + }, + AccountId = currentUser.Id, + UserAgent = Request.Headers.UserAgent.ToString(), + IpAddress = Request.HttpContext.Connection.RemoteIpAddress?.ToString() ?? "" + }); - member.Account = relatedUser; + member.AccountId = Guid.Parse(relatedUser.Id); member.Realm = realm; await rs.SendInviteNotify(member); @@ -125,10 +144,10 @@ public class RealmController( public async Task> AcceptMemberInvite(string slug) { if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); - var userId = currentUser.Id; + var accountId = Guid.Parse(currentUser.Id); var member = await db.RealmMembers - .Where(m => m.AccountId == userId) + .Where(m => m.AccountId == accountId) .Where(m => m.Realm.Slug == slug) .Where(m => m.JoinedAt == null) .FirstOrDefaultAsync(); @@ -138,11 +157,18 @@ public class RealmController( db.Update(member); await db.SaveChangesAsync(); - als.CreateActionLogFromRequest( - ActionLogType.RealmJoin, - new Dictionary { { "realm_id", member.RealmId }, { "account_id", member.AccountId } }, - Request - ); + _ = als.CreateActionLogAsync(new CreateActionLogRequest + { + Action = "realms.members.join", + Meta = + { + { "realm_id", Value.ForString(member.RealmId.ToString()) }, + { "account_id", Value.ForString(member.AccountId.ToString()) } + }, + AccountId = currentUser.Id, + UserAgent = Request.Headers.UserAgent.ToString(), + IpAddress = Request.HttpContext.Connection.RemoteIpAddress?.ToString() ?? "" + }); return Ok(member); } @@ -152,10 +178,10 @@ public class RealmController( public async Task DeclineMemberInvite(string slug) { if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); - var userId = currentUser.Id; + var accountId = Guid.Parse(currentUser.Id); var member = await db.RealmMembers - .Where(m => m.AccountId == userId) + .Where(m => m.AccountId == accountId) .Where(m => m.Realm.Slug == slug) .Where(m => m.JoinedAt == null) .FirstOrDefaultAsync(); @@ -164,11 +190,19 @@ public class RealmController( member.LeaveAt = SystemClock.Instance.GetCurrentInstant(); await db.SaveChangesAsync(); - als.CreateActionLogFromRequest( - ActionLogType.RealmLeave, - new Dictionary { { "realm_id", member.RealmId }, { "account_id", member.AccountId } }, - Request - ); + _ = als.CreateActionLogAsync(new CreateActionLogRequest + { + Action = "realms.members.decline_invite", + Meta = + { + { "realm_id", Value.ForString(member.RealmId.ToString()) }, + { "account_id", Value.ForString(member.AccountId.ToString()) }, + { "decliner_id", Value.ForString(currentUser.Id) } + }, + AccountId = currentUser.Id, + UserAgent = Request.Headers.UserAgent.ToString(), + IpAddress = Request.HttpContext.Connection.RemoteIpAddress?.ToString() ?? "" + }); return NoContent(); } @@ -191,43 +225,41 @@ public class RealmController( if (!realm.IsPublic) { if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); - if (!await rs.IsMemberWithRole(realm.Id, currentUser.Id, RealmMemberRole.Normal)) + if (!await rs.IsMemberWithRole(realm.Id, Guid.Parse(currentUser.Id), RealmMemberRole.Normal)) return StatusCode(403, "You must be a member to view this realm's members."); } - IQueryable query = db.RealmMembers + var query = db.RealmMembers .Where(m => m.RealmId == realm.Id) - .Where(m => m.LeaveAt == null) - .Include(m => m.Account) - .Include(m => m.Account.Profile); + .Where(m => m.LeaveAt == null); - if (withStatus) - { - var members = await query - .OrderBy(m => m.CreatedAt) - .ToListAsync(); - - var memberStatuses = await aes.GetStatuses(members.Select(m => m.AccountId).ToList()); - - if (!string.IsNullOrEmpty(status)) - { - members = members.Where(m => - memberStatuses.TryGetValue(m.AccountId, out var s) && s.Label != null && - s.Label.Equals(status, StringComparison.OrdinalIgnoreCase)).ToList(); - } - - members = members.OrderByDescending(m => memberStatuses.TryGetValue(m.AccountId, out var s) && s.IsOnline) - .ToList(); - - var total = members.Count; - Response.Headers["X-Total"] = total.ToString(); - - var result = members.Skip(offset).Take(take).ToList(); - - return Ok(result); - } - else - { + // if (withStatus) + // { + // var members = await query + // .OrderBy(m => m.CreatedAt) + // .ToListAsync(); + // + // var memberStatuses = await aes.GetStatuses(members.Select(m => m.AccountId).ToList()); + // + // if (!string.IsNullOrEmpty(status)) + // { + // members = members.Where(m => + // memberStatuses.TryGetValue(m.AccountId, out var s) && s.Label != null && + // s.Label.Equals(status, StringComparison.OrdinalIgnoreCase)).ToList(); + // } + // + // members = members.OrderByDescending(m => memberStatuses.TryGetValue(m.AccountId, out var s) && s.IsOnline) + // .ToList(); + // + // var total = members.Count; + // Response.Headers["X-Total"] = total.ToString(); + // + // var result = members.Skip(offset).Take(take).ToList(); + // + // return Ok(result); + // } + // else + // { var total = await query.CountAsync(); Response.Headers["X-Total"] = total.ToString(); @@ -238,23 +270,20 @@ public class RealmController( .ToListAsync(); return Ok(members); - } + // } } - [HttpGet("{slug}/members/me")] [Authorize] public async Task> GetCurrentIdentity(string slug) { if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); - var userId = currentUser.Id; + var accountId = Guid.Parse(currentUser.Id); var member = await db.RealmMembers - .Where(m => m.AccountId == userId) + .Where(m => m.AccountId == accountId) .Where(m => m.Realm.Slug == slug) - .Include(m => m.Account) - .Include(m => m.Account.Profile) .FirstOrDefaultAsync(); if (member is null) return NotFound(); @@ -266,10 +295,10 @@ public class RealmController( public async Task LeaveRealm(string slug) { if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); - var userId = currentUser.Id; + var accountId = Guid.Parse(currentUser.Id); var member = await db.RealmMembers - .Where(m => m.AccountId == userId) + .Where(m => m.AccountId == accountId) .Where(m => m.Realm.Slug == slug) .Where(m => m.JoinedAt != null) .FirstOrDefaultAsync(); @@ -281,11 +310,19 @@ public class RealmController( member.LeaveAt = SystemClock.Instance.GetCurrentInstant(); await db.SaveChangesAsync(); - als.CreateActionLogFromRequest( - ActionLogType.RealmLeave, - new Dictionary { { "realm_id", member.RealmId }, { "account_id", member.AccountId } }, - Request - ); + _ = als.CreateActionLogAsync(new CreateActionLogRequest + { + Action = "realms.members.leave", + Meta = + { + { "realm_id", Value.ForString(member.RealmId.ToString()) }, + { "account_id", Value.ForString(member.AccountId.ToString()) }, + { "leaver_id", Value.ForString(currentUser.Id) } + }, + AccountId = currentUser.Id, + UserAgent = Request.Headers.UserAgent.ToString(), + IpAddress = Request.HttpContext.Connection.RemoteIpAddress?.ToString() ?? "" + }); return NoContent(); } @@ -317,7 +354,7 @@ public class RealmController( Name = request.Name!, Slug = request.Slug!, Description = request.Description!, - AccountId = currentUser.Id, + AccountId = Guid.Parse(currentUser.Id), IsCommunity = request.IsCommunity ?? false, IsPublic = request.IsPublic ?? false, Members = new List @@ -325,7 +362,7 @@ public class RealmController( new() { Role = RealmMemberRole.Owner, - AccountId = currentUser.Id, + AccountId = Guid.Parse(currentUser.Id), JoinedAt = NodaTime.Instant.FromDateTimeUtc(DateTime.UtcNow) } } @@ -333,42 +370,56 @@ public class RealmController( if (request.PictureId is not null) { - realm.Picture = (await db.Files.FindAsync(request.PictureId))?.ToReferenceObject(); - if (realm.Picture is null) return BadRequest("Invalid picture id, unable to find the file on cloud."); + var pictureResult = await files.GetFileAsync(new GetFileRequest { Id = request.PictureId }); + if (pictureResult is null) return BadRequest("Invalid picture id, unable to find the file on cloud."); + realm.Picture = CloudFileReferenceObject.FromProtoValue(pictureResult); } - if (request.BackgroundId is not null) { - realm.Background = (await db.Files.FindAsync(request.BackgroundId))?.ToReferenceObject(); - if (realm.Background is null) return BadRequest("Invalid background id, unable to find the file on cloud."); + var backgroundResult = await files.GetFileAsync(new GetFileRequest { Id = request.BackgroundId }); + if (backgroundResult is null) return BadRequest("Invalid background id, unable to find the file on cloud."); + realm.Background = CloudFileReferenceObject.FromProtoValue(backgroundResult); } db.Realms.Add(realm); await db.SaveChangesAsync(); - als.CreateActionLogFromRequest( - ActionLogType.RealmCreate, - new Dictionary { { "realm_id", realm.Id } }, Request - ); + _ = als.CreateActionLogAsync(new CreateActionLogRequest + { + Action = "realms.create", + Meta = + { + { "realm_id", Value.ForString(realm.Id.ToString()) }, + { "name", Value.ForString(realm.Name) }, + { "slug", Value.ForString(realm.Slug) }, + { "is_community", Value.ForBool(realm.IsCommunity) }, + { "is_public", Value.ForBool(realm.IsPublic) } + }, + AccountId = currentUser.Id, + UserAgent = Request.Headers.UserAgent.ToString(), + IpAddress = Request.HttpContext.Connection.RemoteIpAddress?.ToString() ?? "" + }); var realmResourceId = $"realm:{realm.Id}"; if (realm.Picture is not null) { - await fileRefService.CreateReferenceAsync( - realm.Picture.Id, - "realm.picture", - realmResourceId - ); + await fileRefs.CreateReferenceAsync(new CreateReferenceRequest + { + FileId = realm.Picture.Id, + Usage = "realm.picture", + ResourceId = realmResourceId + }); } if (realm.Background is not null) { - await fileRefService.CreateReferenceAsync( - realm.Background.Id, - "realm.background", - realmResourceId - ); + await fileRefs.CreateReferenceAsync(new CreateReferenceRequest + { + FileId = realm.Background.Id, + Usage = "realm.background", + ResourceId = realmResourceId + }); } return Ok(realm); @@ -385,8 +436,9 @@ public class RealmController( .FirstOrDefaultAsync(); if (realm is null) return NotFound(); + var accountId = Guid.Parse(currentUser.Id); var member = await db.RealmMembers - .Where(m => m.AccountId == currentUser.Id && m.RealmId == realm.Id && m.JoinedAt != null) + .Where(m => m.AccountId == accountId && m.RealmId == realm.Id && m.JoinedAt != null) .FirstOrDefaultAsync(); if (member is null || member.Role < RealmMemberRole.Moderator) return StatusCode(403, "You do not have permission to update this realm."); @@ -409,53 +461,75 @@ public class RealmController( if (request.PictureId is not null) { - var picture = await db.Files.FindAsync(request.PictureId); - if (picture is null) return BadRequest("Invalid picture id, unable to find the file on cloud."); + var pictureResult = await files.GetFileAsync(new GetFileRequest { Id = request.PictureId }); + if (pictureResult is null) return BadRequest("Invalid picture id, unable to find the file on cloud."); // Remove old references for the realm picture if (realm.Picture is not null) { - await fileRefService.DeleteResourceReferencesAsync(realm.ResourceIdentifier, "realm.picture"); + await fileRefs.DeleteResourceReferencesAsync(new DeleteResourceReferencesRequest + { + ResourceId = realm.ResourceIdentifier + }); } - realm.Picture = picture.ToReferenceObject(); + realm.Picture = CloudFileReferenceObject.FromProtoValue(pictureResult); // Create a new reference - await fileRefService.CreateReferenceAsync( - picture.Id, - "realm.picture", - realm.ResourceIdentifier - ); + await fileRefs.CreateReferenceAsync(new CreateReferenceRequest + { + FileId = realm.Picture.Id, + Usage = "realm.picture", + ResourceId = realm.ResourceIdentifier + }); } if (request.BackgroundId is not null) { - var background = await db.Files.FindAsync(request.BackgroundId); - if (background is null) return BadRequest("Invalid background id, unable to find the file on cloud."); + var backgroundResult = await files.GetFileAsync(new GetFileRequest { Id = request.BackgroundId }); + if (backgroundResult is null) return BadRequest("Invalid background id, unable to find the file on cloud."); // Remove old references for the realm background if (realm.Background is not null) { - await fileRefService.DeleteResourceReferencesAsync(realm.ResourceIdentifier, "realm.background"); + await fileRefs.DeleteResourceReferencesAsync(new DeleteResourceReferencesRequest + { + ResourceId = realm.ResourceIdentifier + }); } - realm.Background = background.ToReferenceObject(); + realm.Background = CloudFileReferenceObject.FromProtoValue(backgroundResult); // Create a new reference - await fileRefService.CreateReferenceAsync( - background.Id, - "realm.background", - realm.ResourceIdentifier - ); + await fileRefs.CreateReferenceAsync(new CreateReferenceRequest + { + FileId = realm.Background.Id, + Usage = "realm.background", + ResourceId = realm.ResourceIdentifier + }); } db.Realms.Update(realm); await db.SaveChangesAsync(); - als.CreateActionLogFromRequest( - ActionLogType.RealmUpdate, - new Dictionary { { "realm_id", realm.Id } }, Request - ); + _ = als.CreateActionLogAsync(new CreateActionLogRequest + { + Action = "realms.update", + Meta = + { + { "realm_id", Value.ForString(realm.Id.ToString()) }, + { "name_updated", Value.ForBool(request.Name != null) }, + { "slug_updated", Value.ForBool(request.Slug != null) }, + { "description_updated", Value.ForBool(request.Description != null) }, + { "picture_updated", Value.ForBool(request.PictureId != null) }, + { "background_updated", Value.ForBool(request.BackgroundId != null) }, + { "is_community_updated", Value.ForBool(request.IsCommunity != null) }, + { "is_public_updated", Value.ForBool(request.IsPublic != null) } + }, + AccountId = currentUser.Id, + UserAgent = Request.Headers.UserAgent.ToString(), + IpAddress = Request.HttpContext.Connection.RemoteIpAddress?.ToString() ?? "" + }); return Ok(realm); } @@ -475,14 +549,14 @@ public class RealmController( return StatusCode(403, "Only community realms can be joined without invitation."); var existingMember = await db.RealmMembers - .Where(m => m.AccountId == currentUser.Id && m.RealmId == realm.Id) + .Where(m => m.AccountId == Guid.Parse(currentUser.Id) && m.RealmId == realm.Id) .FirstOrDefaultAsync(); if (existingMember is not null) return BadRequest("You are already a member of this realm."); var member = new RealmMember { - AccountId = currentUser.Id, + AccountId = Guid.Parse(currentUser.Id), RealmId = realm.Id, Role = RealmMemberRole.Normal, JoinedAt = NodaTime.Instant.FromDateTimeUtc(DateTime.UtcNow) @@ -491,11 +565,19 @@ public class RealmController( db.RealmMembers.Add(member); await db.SaveChangesAsync(); - als.CreateActionLogFromRequest( - ActionLogType.RealmJoin, - new Dictionary { { "realm_id", realm.Id }, { "account_id", currentUser.Id } }, - Request - ); + _ = als.CreateActionLogAsync(new CreateActionLogRequest + { + Action = "realms.members.join", + Meta = + { + { "realm_id", Value.ForString(realm.Id.ToString()) }, + { "account_id", Value.ForString(currentUser.Id) }, + { "is_community", Value.ForBool(realm.IsCommunity) } + }, + AccountId = currentUser.Id, + UserAgent = Request.Headers.UserAgent.ToString(), + IpAddress = Request.HttpContext.Connection.RemoteIpAddress?.ToString() ?? "" + }); return Ok(member); } @@ -516,17 +598,25 @@ public class RealmController( .FirstOrDefaultAsync(); if (member is null) return NotFound(); - if (!await rs.IsMemberWithRole(realm.Id, currentUser.Id, RealmMemberRole.Moderator, member.Role)) + if (!await rs.IsMemberWithRole(realm.Id, Guid.Parse(currentUser.Id), RealmMemberRole.Moderator, member.Role)) return StatusCode(403, "You do not have permission to remove members from this realm."); member.LeaveAt = SystemClock.Instance.GetCurrentInstant(); await db.SaveChangesAsync(); - als.CreateActionLogFromRequest( - ActionLogType.ChatroomKick, - new Dictionary { { "realm_id", realm.Id }, { "account_id", memberId } }, - Request - ); + _ = als.CreateActionLogAsync(new CreateActionLogRequest + { + Action = "realms.members.kick", + Meta = + { + { "realm_id", Value.ForString(realm.Id.ToString()) }, + { "account_id", Value.ForString(memberId.ToString()) }, + { "kicker_id", Value.ForString(currentUser.Id) } + }, + AccountId = currentUser.Id, + UserAgent = Request.Headers.UserAgent.ToString(), + IpAddress = Request.HttpContext.Connection.RemoteIpAddress?.ToString() ?? "" + }); return NoContent(); } @@ -545,23 +635,31 @@ public class RealmController( var member = await db.RealmMembers .Where(m => m.AccountId == memberId && m.RealmId == realm.Id) - .Include(m => m.Account) .FirstOrDefaultAsync(); if (member is null) return NotFound(); - if (!await rs.IsMemberWithRole(realm.Id, currentUser.Id, RealmMemberRole.Moderator, member.Role, newRole)) + if (!await rs.IsMemberWithRole(realm.Id, Guid.Parse(currentUser.Id), RealmMemberRole.Moderator, member.Role, + newRole)) return StatusCode(403, "You do not have permission to update member roles in this realm."); member.Role = newRole; db.RealmMembers.Update(member); await db.SaveChangesAsync(); - als.CreateActionLogFromRequest( - ActionLogType.RealmAdjustRole, - new Dictionary - { { "realm_id", realm.Id }, { "account_id", memberId }, { "new_role", newRole } }, - Request - ); + _ = als.CreateActionLogAsync(new CreateActionLogRequest + { + Action = "realms.members.role_update", + Meta = + { + { "realm_id", Value.ForString(realm.Id.ToString()) }, + { "account_id", Value.ForString(memberId.ToString()) }, + { "new_role", Value.ForNumber(newRole) }, + { "updater_id", Value.ForString(currentUser.Id) } + }, + AccountId = currentUser.Id, + UserAgent = Request.Headers.UserAgent.ToString(), + IpAddress = Request.HttpContext.Connection.RemoteIpAddress?.ToString() ?? "" + }); return Ok(member); } @@ -574,26 +672,36 @@ public class RealmController( var realm = await db.Realms .Where(r => r.Slug == slug) - .Include(r => r.Picture) - .Include(r => r.Background) .FirstOrDefaultAsync(); if (realm is null) return NotFound(); - if (!await rs.IsMemberWithRole(realm.Id, currentUser.Id, RealmMemberRole.Owner)) + if (!await rs.IsMemberWithRole(realm.Id, Guid.Parse(currentUser.Id), RealmMemberRole.Owner)) return StatusCode(403, "Only the owner can delete this realm."); db.Realms.Remove(realm); await db.SaveChangesAsync(); - als.CreateActionLogFromRequest( - ActionLogType.RealmDelete, - new Dictionary { { "realm_id", realm.Id } }, Request - ); + _ = als.CreateActionLogAsync(new CreateActionLogRequest + { + Action = "realms.delete", + Meta = + { + { "realm_id", Value.ForString(realm.Id.ToString()) }, + { "realm_name", Value.ForString(realm.Name) }, + { "realm_slug", Value.ForString(realm.Slug) } + }, + AccountId = currentUser.Id, + UserAgent = Request.Headers.UserAgent.ToString(), + IpAddress = Request.HttpContext.Connection.RemoteIpAddress?.ToString() ?? "" + }); // Delete all file references for this realm var realmResourceId = $"realm:{realm.Id}"; - await fileRefService.DeleteResourceReferencesAsync(realmResourceId); + await fileRefs.DeleteResourceReferencesAsync(new DeleteResourceReferencesRequest + { + ResourceId = realmResourceId + }); return NoContent(); } -} +} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Realm/RealmController.cs.bak b/DysonNetwork.Sphere/Realm/RealmController.cs.bak new file mode 100644 index 0000000..449df8e --- /dev/null +++ b/DysonNetwork.Sphere/Realm/RealmController.cs.bak @@ -0,0 +1,696 @@ +using System.ComponentModel.DataAnnotations; +using DysonNetwork.Shared.Data; +using DysonNetwork.Shared.Proto; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using NodaTime; +using Google.Protobuf.WellKnownTypes; + +namespace DysonNetwork.Sphere.Realm; + +[ApiController] +[Route("/api/realms")] +public class RealmController( + AppDatabase db, + RealmService rs, + FileReferenceService.FileReferenceServiceClient fileRefs, + ActionLogService.ActionLogServiceClient als, + AccountService.AccountServiceClient accounts +) : Controller +{ + [HttpGet("{slug}")] + public async Task> GetRealm(string slug) + { + var realm = await db.Realms + .Where(e => e.Slug == slug) + .FirstOrDefaultAsync(); + if (realm is null) return NotFound(); + + return Ok(realm); + } + + [HttpGet] + [Authorize] + public async Task>> ListJoinedRealms() + { + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); + var accountId = Guid.Parse(currentUser.Id); + + var members = await db.RealmMembers + .Where(m => m.AccountId == accountId) + .Where(m => m.JoinedAt != null) + .Where(m => m.LeaveAt == null) + .Include(e => e.Realm) + .Select(m => m.Realm) + .ToListAsync(); + + return members.ToList(); + } + + [HttpGet("invites")] + [Authorize] + public async Task>> ListInvites() + { + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); + var accountId = Guid.Parse(currentUser.Id); + + var members = await db.RealmMembers + .Where(m => m.AccountId == accountId) + .Where(m => m.JoinedAt == null) + .Include(e => e.Realm) + .ToListAsync(); + + return members.ToList(); + } + + public class RealmMemberRequest + { + [Required] public Guid RelatedUserId { get; set; } + [Required] public int Role { get; set; } + } + + [HttpPost("invites/{slug}")] + [Authorize] + public async Task> InviteMember(string slug, + [FromBody] RealmMemberRequest request) + { + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); + var accountId = Guid.Parse(currentUser.Id); + + var relatedUser = + await accounts.GetAccountAsync(new GetAccountRequest { Id = request.RelatedUserId.ToString() }); + if (relatedUser == null) return BadRequest("Related user was not found"); + + var hasBlocked = await accounts.HasRelationshipAsync(new GetRelationshipRequest() + { + AccountId = currentUser.Id, + RelatedId = request.RelatedUserId.ToString(), + Status = -100 + }); + if (hasBlocked?.Value ?? false) + return StatusCode(403, "You cannot invite a user that blocked you."); + + var realm = await db.Realms + .Where(p => p.Slug == slug) + .FirstOrDefaultAsync(); + if (realm is null) return NotFound(); + + if (!await rs.IsMemberWithRole(realm.Id, accountId, request.Role)) + return StatusCode(403, "You cannot invite member has higher permission than yours."); + + var hasExistingMember = await db.RealmMembers + .Where(m => m.AccountId == Guid.Parse(relatedUser.Id)) + .Where(m => m.RealmId == realm.Id) + .Where(m => m.LeaveAt == null) + .AnyAsync(); + if (hasExistingMember) + return BadRequest("This user has been joined the realm or leave cannot be invited again."); + + var member = new RealmMember + { + AccountId = Guid.Parse(relatedUser.Id), + RealmId = realm.Id, + Role = request.Role, + }; + + db.RealmMembers.Add(member); + await db.SaveChangesAsync(); + + _ = als.CreateActionLogAsync(new CreateActionLogRequest + { + Action = "realms.members.invite", + Meta = + { + { "realm_id", Value.ForString(realm.Id.ToString()) }, + { "account_id", Value.ForString(member.AccountId.ToString()) }, + { "role", Value.ForNumber(request.Role) } + }, + AccountId = currentUser.Id, + UserAgent = Request.Headers.UserAgent.ToString(), + IpAddress = Request.HttpContext.Connection.RemoteIpAddress?.ToString() ?? "" + }); + + member.Account = relatedUser; + member.Realm = realm; + await rs.SendInviteNotify(member); + + return Ok(member); + } + + [HttpPost("invites/{slug}/accept")] + [Authorize] + public async Task> AcceptMemberInvite(string slug) + { + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); + var accountId = Guid.Parse(currentUser.Id); + + var member = await db.RealmMembers + .Where(m => m.AccountId == accountId) + .Where(m => m.Realm.Slug == slug) + .Where(m => m.JoinedAt == null) + .FirstOrDefaultAsync(); + if (member is null) return NotFound(); + + member.JoinedAt = NodaTime.Instant.FromDateTimeUtc(DateTime.UtcNow); + db.Update(member); + await db.SaveChangesAsync(); + + _ = als.CreateActionLogAsync(new CreateActionLogRequest + { + Action = "realms.members.join", + Meta = + { + { "realm_id", Value.ForString(member.RealmId.ToString()) }, + { "account_id", Value.ForString(member.AccountId.ToString()) } + }, + AccountId = currentUser.Id, + UserAgent = Request.Headers.UserAgent.ToString(), + IpAddress = Request.HttpContext.Connection.RemoteIpAddress?.ToString() ?? "" + }); + + return Ok(member); + } + + [HttpPost("invites/{slug}/decline")] + [Authorize] + public async Task DeclineMemberInvite(string slug) + { + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); + var accountId = Guid.Parse(currentUser.Id); + + var member = await db.RealmMembers + .Where(m => m.AccountId == accountId) + .Where(m => m.Realm.Slug == slug) + .Where(m => m.JoinedAt == null) + .FirstOrDefaultAsync(); + if (member is null) return NotFound(); + + member.LeaveAt = SystemClock.Instance.GetCurrentInstant(); + await db.SaveChangesAsync(); + + als.CreateActionLogFromRequest( + ActionLogType.RealmLeave, + new Dictionary { { "realm_id", member.RealmId }, { "account_id", member.AccountId } }, + Request + ); + + return NoContent(); + } + + + [HttpGet("{slug}/members")] + public async Task>> ListMembers( + string slug, + [FromQuery] int offset = 0, + [FromQuery] int take = 20, + [FromQuery] bool withStatus = false, + [FromQuery] string? status = null + ) + { + var realm = await db.Realms + .Where(r => r.Slug == slug) + .FirstOrDefaultAsync(); + if (realm is null) return NotFound(); + + if (!realm.IsPublic) + { + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); + if (!await rs.IsMemberWithRole(realm.Id, Guid.Parse(currentUser.Id), RealmMemberRole.Normal)) + return StatusCode(403, "You must be a member to view this realm's members."); + } + + IQueryable query = db.RealmMembers + .Where(m => m.RealmId == realm.Id) + .Where(m => m.LeaveAt == null) + .Include(m => m.Account) + .Include(m => m.Account.Profile); + + if (withStatus) + { + var members = await query + .OrderBy(m => m.CreatedAt) + .ToListAsync(); + + var memberStatuses = await aes.GetStatuses(members.Select(m => m.AccountId).ToList()); + + if (!string.IsNullOrEmpty(status)) + { + members = members.Where(m => + memberStatuses.TryGetValue(m.AccountId, out var s) && s.Label != null && + s.Label.Equals(status, StringComparison.OrdinalIgnoreCase)).ToList(); + } + + members = members.OrderByDescending(m => memberStatuses.TryGetValue(m.AccountId, out var s) && s.IsOnline) + .ToList(); + + var total = members.Count; + Response.Headers["X-Total"] = total.ToString(); + + var result = members.Skip(offset).Take(take).ToList(); + + return Ok(result); + } + else + { + var total = await query.CountAsync(); + Response.Headers["X-Total"] = total.ToString(); + + var members = await query + .OrderBy(m => m.CreatedAt) + .Skip(offset) + .Take(take) + .ToListAsync(); + + return Ok(members); + } + } + + + [HttpGet("{slug}/members/me")] + [Authorize] + public async Task> GetCurrentIdentity(string slug) + { + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); + var accountId = Guid.Parse(currentUser.Id); + + var member = await db.RealmMembers + .Where(m => m.AccountId == accountId) + .Where(m => m.Realm.Slug == slug) + .FirstOrDefaultAsync(); + + if (member is null) return NotFound(); + return Ok(member); + } + + [HttpDelete("{slug}/members/me")] + [Authorize] + public async Task LeaveRealm(string slug) + { + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); + var accountId = Guid.Parse(currentUser.Id); + + var member = await db.RealmMembers + .Where(m => m.AccountId == accountId) + .Where(m => m.Realm.Slug == slug) + .Where(m => m.JoinedAt != null) + .FirstOrDefaultAsync(); + if (member is null) return NotFound(); + + if (member.Role == RealmMemberRole.Owner) + return StatusCode(403, "Owner cannot leave their own realm."); + + member.LeaveAt = SystemClock.Instance.GetCurrentInstant(); + await db.SaveChangesAsync(); + + als.CreateActionLogFromRequest( + ActionLogType.RealmLeave, + new Dictionary { { "realm_id", member.RealmId }, { "account_id", member.AccountId } }, + Request + ); + + return NoContent(); + } + + public class RealmRequest + { + [MaxLength(1024)] public string? Slug { get; set; } + [MaxLength(1024)] public string? Name { get; set; } + [MaxLength(4096)] public string? Description { get; set; } + public string? PictureId { get; set; } + public string? BackgroundId { get; set; } + public bool? IsCommunity { get; set; } + public bool? IsPublic { get; set; } + } + + [HttpPost] + [Authorize] + public async Task> CreateRealm(RealmRequest request) + { + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); + if (string.IsNullOrWhiteSpace(request.Name)) return BadRequest("You cannot create a realm without a name."); + if (string.IsNullOrWhiteSpace(request.Slug)) return BadRequest("You cannot create a realm without a slug."); + + var slugExists = await db.Realms.AnyAsync(r => r.Slug == request.Slug); + if (slugExists) return BadRequest("Realm with this slug already exists."); + + var realm = new Realm + { + Name = request.Name!, + Slug = request.Slug!, + Description = request.Description!, + AccountId = currentUser.Id, + IsCommunity = request.IsCommunity ?? false, + IsPublic = request.IsPublic ?? false, + Members = new List + { + new() + { + Role = RealmMemberRole.Owner, + AccountId = Guid.Parse(currentUser.Id), + JoinedAt = NodaTime.Instant.FromDateTimeUtc(DateTime.UtcNow) + } + } + }; + + if (request.PictureId is not null) + { + var pictureResult = await files.GetFileAsync(new GetFileRequest { Id = request.PictureId }); + if (pictureResult is null) return BadRequest("Invalid picture id, unable to find the file on cloud."); + realm.Picture = CloudFileReferenceObject.FromProtoValue(pictureResult); + } + + if (request.BackgroundId is not null) + { + var backgroundResult = await files.GetFileAsync(new GetFileRequest { Id = request.BackgroundId }); + if (backgroundResult is null) return BadRequest("Invalid background id, unable to find the file on cloud."); + realm.Background = CloudFileReferenceObject.FromProtoValue(backgroundResult); + } + + db.Realms.Add(realm); + await db.SaveChangesAsync(); + + _ = als.CreateActionLogAsync(new CreateActionLogRequest + { + Action = "realms.create", + Meta = + { + { "realm_id", Value.ForString(realm.Id.ToString()) }, + { "name", Value.ForString(realm.Name) }, + { "slug", Value.ForString(realm.Slug) }, + { "is_community", Value.ForBool(realm.IsCommunity) }, + { "is_public", Value.ForBool(realm.IsPublic) } + }, + UserId = currentUser.Id, + UserAgent = Request.Headers.UserAgent.ToString(), + IpAddress = Request.HttpContext.Connection.RemoteIpAddress?.ToString() ?? "" + }); + + var realmResourceId = $"realm:{realm.Id}"; + + if (realm.Picture is not null) + { + await fileRefs.CreateReferenceAsync(new CreateReferenceRequest + { + FileId = realm.Picture.Id, + Usage = "realm.picture", + ResourceId = realmResourceId + }); + } + + if (realm.Background is not null) + { + await fileRefs.CreateReferenceAsync(new CreateReferenceRequest + { + FileId = realm.Background.Id, + Usage = "realm.background", + ResourceId = realmResourceId + }); + } + + return Ok(realm); + } + + [HttpPatch("{slug}")] + [Authorize] + public async Task> Update(string slug, [FromBody] RealmRequest request) + { + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); + + var realm = await db.Realms + .Where(r => r.Slug == slug) + .FirstOrDefaultAsync(); + if (realm is null) return NotFound(); + + var accountId = Guid.Parse(currentUser.Id); + var member = await db.RealmMembers + .Where(m => m.AccountId == accountId && m.RealmId == realm.Id && m.JoinedAt != null) + .FirstOrDefaultAsync(); + if (member is null || member.Role < RealmMemberRole.Moderator) + return StatusCode(403, "You do not have permission to update this realm."); + + if (request.Slug is not null && request.Slug != realm.Slug) + { + var slugExists = await db.Realms.AnyAsync(r => r.Slug == request.Slug); + if (slugExists) return BadRequest("Realm with this slug already exists."); + realm.Slug = request.Slug; + } + + if (request.Name is not null) + realm.Name = request.Name; + if (request.Description is not null) + realm.Description = request.Description; + if (request.IsCommunity is not null) + realm.IsCommunity = request.IsCommunity.Value; + if (request.IsPublic is not null) + realm.IsPublic = request.IsPublic.Value; + + if (request.PictureId is not null) + { + var pictureResult = await files.GetFileAsync(new GetFileRequest { Id = request.PictureId }); + if (pictureResult is null) return BadRequest("Invalid picture id, unable to find the file on cloud."); + + // Remove old references for the realm picture + if (realm.Picture is not null) + { + await fileRefs.DeleteResourceReferencesAsync(new DeleteResourceReferencesRequest + { + ResourceId = realm.ResourceIdentifier + }); + } + + realm.Picture = CloudFileReferenceObject.FromProtoValue(pictureResult); + + // Create a new reference + await fileRefs.CreateReferenceAsync(new CreateReferenceRequest + { + FileId = realm.Picture.Id, + Usage = "realm.picture", + ResourceId = realm.ResourceIdentifier + }); + } + + if (request.BackgroundId is not null) + { + var backgroundResult = await files.GetFileAsync(new GetFileRequest { Id = request.BackgroundId }); + if (backgroundResult is null) return BadRequest("Invalid background id, unable to find the file on cloud."); + + // Remove old references for the realm background + if (realm.Background is not null) + { + await fileRefs.DeleteResourceReferencesAsync(new DeleteResourceReferencesRequest + { + ResourceId = realm.ResourceIdentifier + }); + } + + realm.Background = CloudFileReferenceObject.FromProtoValue(backgroundResult); + + // Create a new reference + await fileRefs.CreateReferenceAsync(new CreateReferenceRequest + { + FileId = realm.Background.Id, + Usage = "realm.background", + ResourceId = realm.ResourceIdentifier + }); + } + + db.Realms.Update(realm); + await db.SaveChangesAsync(); + + _ = als.CreateActionLogAsync(new CreateActionLogRequest + { + Action = "realms.update", + Meta = + { + { "realm_id", Value.ForString(realm.Id.ToString()) }, + { "name_updated", Value.ForBool(request.Name != null) }, + { "slug_updated", Value.ForBool(request.Slug != null) }, + { "description_updated", Value.ForBool(request.Description != null) }, + { "picture_updated", Value.ForBool(request.PictureId != null) }, + { "background_updated", Value.ForBool(request.BackgroundId != null) }, + { "is_community_updated", Value.ForBool(request.IsCommunity != null) }, + { "is_public_updated", Value.ForBool(request.IsPublic != null) } + }, + UserId = currentUser.Id, + UserAgent = Request.Headers.UserAgent.ToString(), + IpAddress = Request.HttpContext.Connection.RemoteIpAddress?.ToString() ?? "" + }); + + return Ok(realm); + } + + [HttpPost("{slug}/members/me")] + [Authorize] + public async Task> JoinRealm(string slug) + { + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); + + var realm = await db.Realms + .Where(r => r.Slug == slug) + .FirstOrDefaultAsync(); + if (realm is null) return NotFound(); + + if (!realm.IsCommunity) + return StatusCode(403, "Only community realms can be joined without invitation."); + + var existingMember = await db.RealmMembers + .Where(m => m.AccountId == Guid.Parse(currentUser.Id) && m.RealmId == realm.Id) + .FirstOrDefaultAsync(); + if (existingMember is not null) + return BadRequest("You are already a member of this realm."); + + var member = new RealmMember + { + AccountId = currentUser.Id, + RealmId = realm.Id, + Role = RealmMemberRole.Normal, + JoinedAt = NodaTime.Instant.FromDateTimeUtc(DateTime.UtcNow) + }; + + db.RealmMembers.Add(member); + await db.SaveChangesAsync(); + + _ = als.CreateActionLogAsync(new CreateActionLogRequest + { + Action = "realms.members.join", + Meta = + { + { "realm_id", Value.ForString(realm.Id.ToString()) }, + { "account_id", Value.ForString(currentUser.Id) }, + { "is_community", Value.ForBool(realm.IsCommunity) } + }, + UserId = currentUser.Id, + UserAgent = Request.Headers.UserAgent.ToString(), + IpAddress = Request.HttpContext.Connection.RemoteIpAddress?.ToString() ?? "" + }); + + return Ok(member); + } + + [HttpDelete("{slug}/members/{memberId:guid}")] + [Authorize] + public async Task RemoveMember(string slug, Guid memberId) + { + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); + + var realm = await db.Realms + .Where(r => r.Slug == slug) + .FirstOrDefaultAsync(); + if (realm is null) return NotFound(); + + var member = await db.RealmMembers + .Where(m => m.AccountId == memberId && m.RealmId == realm.Id) + .FirstOrDefaultAsync(); + if (member is null) return NotFound(); + + if (!await rs.IsMemberWithRole(realm.Id, Guid.Parse(currentUser.Id), RealmMemberRole.Moderator, member.Role)) + return StatusCode(403, "You do not have permission to remove members from this realm."); + + member.LeaveAt = SystemClock.Instance.GetCurrentInstant(); + await db.SaveChangesAsync(); + + _ = als.CreateActionLogAsync(new CreateActionLogRequest + { + Action = "realms.members.kick", + Meta = + { + { "realm_id", Value.ForString(realm.Id.ToString()) }, + { "account_id", Value.ForString(memberId.ToString()) }, + { "kicker_id", Value.ForString(currentUser.Id) } + }, + UserId = currentUser.Id, + UserAgent = Request.Headers.UserAgent.ToString(), + IpAddress = Request.HttpContext.Connection.RemoteIpAddress?.ToString() ?? "" + }); + + return NoContent(); + } + + [HttpPatch("{slug}/members/{memberId:guid}/role")] + [Authorize] + public async Task> UpdateMemberRole(string slug, Guid memberId, [FromBody] int newRole) + { + if (newRole >= RealmMemberRole.Owner) return BadRequest("Unable to set realm member to owner or greater role."); + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); + + var realm = await db.Realms + .Where(r => r.Slug == slug) + .FirstOrDefaultAsync(); + if (realm is null) return NotFound(); + + var member = await db.RealmMembers + .Where(m => m.AccountId == memberId && m.RealmId == realm.Id) + .Include(m => m.Account) + .FirstOrDefaultAsync(); + if (member is null) return NotFound(); + + if (!await rs.IsMemberWithRole(realm.Id, Guid.Parse(currentUser.Id), RealmMemberRole.Moderator, member.Role, + newRole)) + return StatusCode(403, "You do not have permission to update member roles in this realm."); + + member.Role = newRole; + db.RealmMembers.Update(member); + await db.SaveChangesAsync(); + + _ = als.CreateActionLogAsync(new CreateActionLogRequest + { + Action = "realms.members.role_update", + Meta = + { + { "realm_id", Value.ForString(realm.Id.ToString()) }, + { "account_id", Value.ForString(memberId.ToString()) }, + { "new_role", Value.ForNumber(newRole) }, + { "updater_id", Value.ForString(currentUser.Id) } + }, + UserId = currentUser.Id, + UserAgent = Request.Headers.UserAgent.ToString(), + IpAddress = Request.HttpContext.Connection.RemoteIpAddress?.ToString() ?? "" + }); + + return Ok(member); + } + + [HttpDelete("{slug}")] + [Authorize] + public async Task Delete(string slug) + { + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); + + var realm = await db.Realms + .Where(r => r.Slug == slug) + .Include(r => r.Picture) + .Include(r => r.Background) + .FirstOrDefaultAsync(); + if (realm is null) return NotFound(); + + if (!await rs.IsMemberWithRole(realm.Id, Guid.Parse(currentUser.Id), RealmMemberRole.Owner)) + return StatusCode(403, "Only the owner can delete this realm."); + + db.Realms.Remove(realm); + await db.SaveChangesAsync(); + + _ = als.CreateActionLogAsync(new CreateActionLogRequest + { + Action = "realms.delete", + Meta = + { + { "realm_id", Value.ForString(realm.Id.ToString()) }, + { "realm_name", Value.ForString(realm.Name) }, + { "realm_slug", Value.ForString(realm.Slug) } + }, + UserId = currentUser.Id, + UserAgent = Request.Headers.UserAgent.ToString(), + IpAddress = Request.HttpContext.Connection.RemoteIpAddress?.ToString() ?? "" + }); + + // Delete all file references for this realm + var realmResourceId = $"realm:{realm.Id}"; + await fileRefs.DeleteResourceReferencesAsync(new DeleteResourceReferencesRequest + { + ResourceId = realmResourceId + }); + + return NoContent(); + } +} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Realm/RealmService.cs b/DysonNetwork.Sphere/Realm/RealmService.cs index 605d23d..b5b1a5e 100644 --- a/DysonNetwork.Sphere/Realm/RealmService.cs +++ b/DysonNetwork.Sphere/Realm/RealmService.cs @@ -1,22 +1,36 @@ -using DysonNetwork.Sphere.Account; +using DysonNetwork.Shared; +using DysonNetwork.Shared.Proto; using DysonNetwork.Sphere.Localization; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Localization; namespace DysonNetwork.Sphere.Realm; -public class RealmService(AppDatabase db, NotificationService nty, IStringLocalizer localizer) +public class RealmService( + AppDatabase db, + PusherService.PusherServiceClient pusher, + AccountService.AccountServiceClient accounts, + IStringLocalizer localizer +) { public async Task SendInviteNotify(RealmMember member) { - AccountService.SetCultureInfo(member.Account); - await nty.SendNotification( - member.Account, - "invites.realms", - localizer["RealmInviteTitle"], - null, - localizer["RealmInviteBody", member.Realm.Name], - actionUri: "/realms" + var account = await accounts.GetAccountAsync(new GetAccountRequest { Id = member.AccountId.ToString() }); + CultureService.SetCultureInfo(account); + + await pusher.SendPushNotificationToUserAsync( + new SendPushNotificationToUserRequest + { + UserId = account.Id, + Notification = new PushNotification + { + Topic = "invites.realms", + Title = localizer["RealmInviteTitle"], + Body = localizer["RealmInviteBody", member.Realm.Name], + ActionUri = "/realms", + IsSavable = true + } + } ); } diff --git a/DysonNetwork.Sphere/Startup/ApplicationConfiguration.cs b/DysonNetwork.Sphere/Startup/ApplicationConfiguration.cs index d71d898..ee62b91 100644 --- a/DysonNetwork.Sphere/Startup/ApplicationConfiguration.cs +++ b/DysonNetwork.Sphere/Startup/ApplicationConfiguration.cs @@ -1,21 +1,17 @@ using System.Net; -using DysonNetwork.Sphere.Connection; -using DysonNetwork.Sphere.Permission; -using DysonNetwork.Sphere.Storage; +using DysonNetwork.Shared.Auth; using Microsoft.AspNetCore.HttpOverrides; using Prometheus; using tusdotnet; -using tusdotnet.Stores; namespace DysonNetwork.Sphere.Startup; public static class ApplicationConfiguration { - public static WebApplication ConfigureAppMiddleware(this WebApplication app, IConfiguration configuration, TusDiskStore tusDiskStore) + public static WebApplication ConfigureAppMiddleware(this WebApplication app, IConfiguration configuration) { app.MapMetrics(); app.MapOpenApi(); - app.UseMiddleware(); app.UseSwagger(); app.UseSwaggerUI(); @@ -44,8 +40,6 @@ public static class ApplicationConfiguration app.MapStaticAssets().RequireRateLimiting("fixed"); app.MapRazorPages().RequireRateLimiting("fixed"); - app.MapTus("/files/tus", _ => Task.FromResult(TusService.BuildConfiguration(tusDiskStore))); - return app; } diff --git a/DysonNetwork.Sphere/Startup/ScheduledJobsConfiguration.cs b/DysonNetwork.Sphere/Startup/ScheduledJobsConfiguration.cs index 43cd0b1..521a9c6 100644 --- a/DysonNetwork.Sphere/Startup/ScheduledJobsConfiguration.cs +++ b/DysonNetwork.Sphere/Startup/ScheduledJobsConfiguration.cs @@ -1,7 +1,4 @@ -using DysonNetwork.Sphere.Storage; using DysonNetwork.Sphere.WebReader; -using DysonNetwork.Sphere.Storage.Handlers; -using DysonNetwork.Sphere.Wallet; using Quartz; namespace DysonNetwork.Sphere.Startup; @@ -19,63 +16,15 @@ public static class ScheduledJobsConfiguration .WithIdentity("AppDatabaseRecyclingTrigger") .WithCronSchedule("0 0 0 * * ?")); - var cloudFilesRecyclingJob = new JobKey("CloudFilesUnusedRecycling"); - q.AddJob(opts => opts.WithIdentity(cloudFilesRecyclingJob)); - q.AddTrigger(opts => opts - .ForJob(cloudFilesRecyclingJob) - .WithIdentity("CloudFilesUnusedRecyclingTrigger") - .WithSimpleSchedule(o => o.WithIntervalInHours(1).RepeatForever()) - ); - - var actionLogFlushJob = new JobKey("ActionLogFlush"); - q.AddJob(opts => opts.WithIdentity(actionLogFlushJob)); - q.AddTrigger(opts => opts - .ForJob(actionLogFlushJob) - .WithIdentity("ActionLogFlushTrigger") - .WithSimpleSchedule(o => o - .WithIntervalInMinutes(5) - .RepeatForever()) - ); - - var readReceiptFlushJob = new JobKey("ReadReceiptFlush"); - q.AddJob(opts => opts.WithIdentity(readReceiptFlushJob)); - q.AddTrigger(opts => opts - .ForJob(readReceiptFlushJob) - .WithIdentity("ReadReceiptFlushTrigger") - .WithSimpleSchedule(o => o - .WithIntervalInSeconds(60) - .RepeatForever()) - ); - - var lastActiveFlushJob = new JobKey("LastActiveFlush"); - q.AddJob(opts => opts.WithIdentity(lastActiveFlushJob)); - q.AddTrigger(opts => opts - .ForJob(lastActiveFlushJob) - .WithIdentity("LastActiveFlushTrigger") - .WithSimpleSchedule(o => o - .WithIntervalInMinutes(5) - .RepeatForever()) - ); - - var postViewFlushJob = new JobKey("PostViewFlush"); - q.AddJob(opts => opts.WithIdentity(postViewFlushJob)); - q.AddTrigger(opts => opts - .ForJob(postViewFlushJob) - .WithIdentity("PostViewFlushTrigger") - .WithSimpleSchedule(o => o - .WithIntervalInMinutes(1) - .RepeatForever()) - ); - - var subscriptionRenewalJob = new JobKey("SubscriptionRenewal"); - q.AddJob(opts => opts.WithIdentity(subscriptionRenewalJob)); - q.AddTrigger(opts => opts - .ForJob(subscriptionRenewalJob) - .WithIdentity("SubscriptionRenewalTrigger") - .WithSimpleSchedule(o => o - .WithIntervalInMinutes(30) - .RepeatForever()) - ); + // var postViewFlushJob = new JobKey("PostViewFlush"); + // q.AddJob(opts => opts.WithIdentity(postViewFlushJob)); + // q.AddTrigger(opts => opts + // .ForJob(postViewFlushJob) + // .WithIdentity("PostViewFlushTrigger") + // .WithSimpleSchedule(o => o + // .WithIntervalInMinutes(1) + // .RepeatForever()) + // ); var webFeedScraperJob = new JobKey("WebFeedScraper"); q.AddJob(opts => opts.WithIdentity(webFeedScraperJob)); diff --git a/DysonNetwork.Sphere/Startup/ServiceCollectionExtensions.cs b/DysonNetwork.Sphere/Startup/ServiceCollectionExtensions.cs index 502039f..d4dfa39 100644 --- a/DysonNetwork.Sphere/Startup/ServiceCollectionExtensions.cs +++ b/DysonNetwork.Sphere/Startup/ServiceCollectionExtensions.cs @@ -21,9 +21,7 @@ using DysonNetwork.Shared.Proto; using DysonNetwork.Sphere.WebReader; using DysonNetwork.Sphere.Developer; using DysonNetwork.Sphere.Discovery; -using DysonNetwork.Sphere.Safety; using tusdotnet.Stores; -using PermissionService = DysonNetwork.Sphere.Permission.PermissionService; namespace DysonNetwork.Sphere.Startup; @@ -90,12 +88,6 @@ public static class ServiceCollectionExtensions { services.AddCors(); services.AddAuthorization(); - services.AddAuthentication(options => - { - options.DefaultAuthenticateScheme = AuthConstants.SchemeName; - options.DefaultChallengeScheme = AuthConstants.SchemeName; - }) - .AddScheme(AuthConstants.SchemeName, _ => { }); return services; } @@ -146,17 +138,6 @@ public static class ServiceCollectionExtensions return services; } - public static IServiceCollection AddAppFileStorage(this IServiceCollection services, IConfiguration configuration) - { - var tusStorePath = configuration.GetSection("Tus").GetValue("StorePath")!; - Directory.CreateDirectory(tusStorePath); - var tusDiskStore = new TusDiskStore(tusStorePath); - - services.AddSingleton(tusDiskStore); - - return services; - } - public static IServiceCollection AddAppFlushHandlers(this IServiceCollection services) { services.AddSingleton(); @@ -169,7 +150,6 @@ public static class ServiceCollectionExtensions { services.Configure(configuration.GetSection("GeoIP")); services.AddScoped(); - services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); @@ -181,7 +161,6 @@ public static class ServiceCollectionExtensions services.AddScoped(); services.AddScoped(); services.AddScoped(); - services.AddScoped(); services.AddScoped(); services.AddScoped(); diff --git a/DysonNetwork.Sphere/Sticker/StickerController.cs b/DysonNetwork.Sphere/Sticker/StickerController.cs index 7bbb840..e82288e 100644 --- a/DysonNetwork.Sphere/Sticker/StickerController.cs +++ b/DysonNetwork.Sphere/Sticker/StickerController.cs @@ -1,7 +1,7 @@ using System.ComponentModel.DataAnnotations; +using DysonNetwork.Shared.Auth; using DysonNetwork.Shared.Data; using DysonNetwork.Shared.Proto; -using DysonNetwork.Sphere.Permission; using DysonNetwork.Sphere.Publisher; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; diff --git a/DysonNetwork.Sphere/Sticker/StickerService.cs b/DysonNetwork.Sphere/Sticker/StickerService.cs index cd4d01e..d912a10 100644 --- a/DysonNetwork.Sphere/Sticker/StickerService.cs +++ b/DysonNetwork.Sphere/Sticker/StickerService.cs @@ -23,12 +23,12 @@ public class StickerService( db.Stickers.Add(sticker); await db.SaveChangesAsync(); - var stickerResourceId = $"sticker:{sticker.Id}"; - await fileRefService.CreateReferenceAsync( - sticker.Image.Id, - StickerFileUsageIdentifier, - stickerResourceId - ); + await fileRefs.CreateReferenceAsync(new CreateReferenceRequest + { + FileId = sticker.Image.Id, + Usage = StickerFileUsageIdentifier, + ResourceId = sticker.ResourceIdentifier + }); return sticker; } @@ -37,24 +37,17 @@ public class StickerService( { if (newImage is not null) { - var stickerResourceId = $"sticker:{sticker.Id}"; + await fileRefs.DeleteResourceReferencesAsync(new DeleteResourceReferencesRequest { ResourceId = sticker.ResourceIdentifier }); - // Delete old references - var oldRefs = - await fileRefService.GetResourceReferencesAsync(stickerResourceId, StickerFileUsageIdentifier); - foreach (var oldRef in oldRefs) - { - await fileRefService.DeleteReferenceAsync(oldRef.Id); - } - - sticker.Image = newImage.ToReferenceObject(); + sticker.Image = newImage; // Create new reference - await fileRefService.CreateReferenceAsync( - newImage.Id, - StickerFileUsageIdentifier, - stickerResourceId - ); + await fileRefs.CreateReferenceAsync(new CreateReferenceRequest + { + FileId = newImage.Id, + Usage = StickerFileUsageIdentifier, + ResourceId = sticker.ResourceIdentifier + }); } db.Stickers.Update(sticker); @@ -71,7 +64,7 @@ public class StickerService( var stickerResourceId = $"sticker:{sticker.Id}"; // Delete all file references for this sticker - await fileRefService.DeleteResourceReferencesAsync(stickerResourceId); + await fileRefs.DeleteResourceReferencesAsync(new DeleteResourceReferencesRequest { ResourceId = stickerResourceId }); db.Stickers.Remove(sticker); await db.SaveChangesAsync(); @@ -90,13 +83,11 @@ public class StickerService( // Delete all file references for each sticker in the pack foreach (var stickerResourceId in stickers.Select(sticker => $"sticker:{sticker.Id}")) - { - await fileRefService.DeleteResourceReferencesAsync(stickerResourceId); - } + await fileRefs.DeleteResourceReferencesAsync(new DeleteResourceReferencesRequest { ResourceId = stickerResourceId }); // Delete any references for the pack itself var packResourceId = $"stickerpack:{pack.Id}"; - await fileRefService.DeleteResourceReferencesAsync(packResourceId); + await fileRefs.DeleteResourceReferencesAsync(new DeleteResourceReferencesRequest { ResourceId = packResourceId }); db.Stickers.RemoveRange(stickers); db.StickerPacks.Remove(pack); diff --git a/DysonNetwork.Sphere/WebReader/WebReaderController.cs b/DysonNetwork.Sphere/WebReader/WebReaderController.cs index c400d0e..0f6f266 100644 --- a/DysonNetwork.Sphere/WebReader/WebReaderController.cs +++ b/DysonNetwork.Sphere/WebReader/WebReaderController.cs @@ -1,4 +1,4 @@ -using DysonNetwork.Sphere.Permission; +using DysonNetwork.Shared.Auth; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.RateLimiting; diff --git a/DysonNetwork.Sphere/appsettings.json b/DysonNetwork.Sphere/appsettings.json index 2437c68..f7023f6 100644 --- a/DysonNetwork.Sphere/appsettings.json +++ b/DysonNetwork.Sphere/appsettings.json @@ -13,84 +13,6 @@ "FastRetrieve": "localhost:6379", "Etcd": "etcd.orb.local:2379" }, - "Authentication": { - "Schemes": { - "Bearer": { - "ValidAudiences": [ - "http://localhost:5071", - "https://localhost:7099" - ], - "ValidIssuer": "solar-network" - } - } - }, - "AuthToken": { - "PublicKeyPath": "Keys/PublicKey.pem", - "PrivateKeyPath": "Keys/PrivateKey.pem" - }, - "OidcProvider": { - "IssuerUri": "https://nt.solian.app", - "PublicKeyPath": "Keys/PublicKey.pem", - "PrivateKeyPath": "Keys/PrivateKey.pem", - "AccessTokenLifetime": "01:00:00", - "RefreshTokenLifetime": "30.00:00:00", - "AuthorizationCodeLifetime": "00:30:00", - "RequireHttpsMetadata": true - }, - "Tus": { - "StorePath": "Uploads" - }, - "Storage": { - "PreferredRemote": "minio", - "Remote": [ - { - "Id": "minio", - "Label": "Minio", - "Region": "auto", - "Bucket": "solar-network-development", - "Endpoint": "localhost:9000", - "SecretId": "littlesheep", - "SecretKey": "password", - "EnabledSigned": true, - "EnableSsl": false - }, - { - "Id": "cloudflare", - "Label": "Cloudflare R2", - "Region": "auto", - "Bucket": "solar-network", - "Endpoint": "0a70a6d1b7128888c823359d0008f4e1.r2.cloudflarestorage.com", - "SecretId": "8ff5d06c7b1639829d60bc6838a542e6", - "SecretKey": "fd58158c5201be16d1872c9209d9cf199421dae3c2f9972f94b2305976580d67", - "EnableSigned": true, - "EnableSsl": true - } - ] - }, - "Captcha": { - "Provider": "cloudflare", - "ApiKey": "0x4AAAAAABCDUdOujj4feOb_", - "ApiSecret": "0x4AAAAAABCDUWABiJQweqlB7tYq-IqIm8U" - }, - "Notifications": { - "Topic": "dev.solsynth.solian", - "Endpoint": "http://localhost:8088" - }, - "Email": { - "Server": "smtp4dev.orb.local", - "Port": 25, - "UseSsl": false, - "Username": "no-reply@mail.solsynth.dev", - "Password": "password", - "FromAddress": "no-reply@mail.solsynth.dev", - "FromName": "Alphabot", - "SubjectPrefix": "Solar Network" - }, - "RealtimeChat": { - "Endpoint": "https://solar-network-im44o8gq.livekit.cloud", - "ApiKey": "APIs6TiL8wj3A4j", - "ApiSecret": "SffxRneIwTnlHPtEf3zicmmv3LUEl7xXael4PvWZrEhE" - }, "GeoIp": { "DatabasePath": "./Keys/GeoLite2-City.mmdb" }, @@ -111,23 +33,17 @@ "DiscoveryEndpoint": "YOUR_MICROSOFT_DISCOVERY_ENDPOINT" } }, - "Payment": { - "Auth": { - "Afdian": "" - }, - "Subscriptions": { - "Afdian": { - "7d17aae23c9611f0b5705254001e7c00": "solian.stellar.primary", - "7dfae4743c9611f0b3a55254001e7c00": "solian.stellar.nova", - "141713ee3d6211f085b352540025c377": "solian.stellar.supernova" - } - } - }, "KnownProxies": [ "127.0.0.1", "::1" ], "Etcd": { "Insecure": true + }, + "Service": { + "Name": "DysonNetwork.Sphere", + "Url": "https://localhost:7099", + "ClientCert": "../Certificates/client.crt", + "ClientKey": "../Certificates/client.key" } } From 21b42b5b219ae265992c5ce445dd454ce59e3b0a Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Tue, 15 Jul 2025 18:30:53 +0800 Subject: [PATCH 21/42] :tada: Initial commit for DysonNetwork.Gateway --- .../Controllers/WellKnownController.cs | 16 +++ DysonNetwork.Gateway/Dockerfile | 23 ++++ .../DysonNetwork.Gateway.csproj | 19 +++ .../EtcdProxyConfigProvider.cs | 108 ++++++++++++++++++ DysonNetwork.Gateway/Program.cs | 15 +++ .../Properties/launchSettings.json | 23 ++++ .../Startup/ServiceCollectionExtensions.cs | 16 +++ DysonNetwork.Gateway/appsettings.json | 25 ++++ DysonNetwork.Shared/Auth/Startup.cs | 18 ++- DysonNetwork.Shared/Proto/GrpcClientHelper.cs | 12 ++ DysonNetwork.sln | 7 ++ compose.yaml | 95 ++++++++++++++- 12 files changed, 372 insertions(+), 5 deletions(-) create mode 100644 DysonNetwork.Gateway/Controllers/WellKnownController.cs create mode 100644 DysonNetwork.Gateway/Dockerfile create mode 100644 DysonNetwork.Gateway/DysonNetwork.Gateway.csproj create mode 100644 DysonNetwork.Gateway/EtcdProxyConfigProvider.cs create mode 100644 DysonNetwork.Gateway/Program.cs create mode 100644 DysonNetwork.Gateway/Properties/launchSettings.json create mode 100644 DysonNetwork.Gateway/Startup/ServiceCollectionExtensions.cs create mode 100644 DysonNetwork.Gateway/appsettings.json diff --git a/DysonNetwork.Gateway/Controllers/WellKnownController.cs b/DysonNetwork.Gateway/Controllers/WellKnownController.cs new file mode 100644 index 0000000..7e94544 --- /dev/null +++ b/DysonNetwork.Gateway/Controllers/WellKnownController.cs @@ -0,0 +1,16 @@ +using Microsoft.AspNetCore.Mvc; + +namespace DysonNetwork.Gateway.Controllers; + +[ApiController] +[Route("/.well-known")] +public class WellKnownController(IConfiguration configuration) : ControllerBase +{ + [HttpGet("domains")] + public IActionResult GetDomainMappings() + { + var domainMappings = configuration.GetSection("DomainMappings").GetChildren() + .ToDictionary(x => x.Key, x => x.Value); + return Ok(domainMappings); + } +} \ No newline at end of file diff --git a/DysonNetwork.Gateway/Dockerfile b/DysonNetwork.Gateway/Dockerfile new file mode 100644 index 0000000..b9273da --- /dev/null +++ b/DysonNetwork.Gateway/Dockerfile @@ -0,0 +1,23 @@ +FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base +USER $APP_UID +WORKDIR /app +EXPOSE 8080 +EXPOSE 8081 + +FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build +ARG BUILD_CONFIGURATION=Release +WORKDIR /src +COPY ["DysonNetwork.Gateway/DysonNetwork.Gateway.csproj", "DysonNetwork.Gateway/"] +RUN dotnet restore "DysonNetwork.Gateway/DysonNetwork.Gateway.csproj" +COPY . . +WORKDIR "/src/DysonNetwork.Gateway" +RUN dotnet build "./DysonNetwork.Gateway.csproj" -c $BUILD_CONFIGURATION -o /app/build + +FROM build AS publish +ARG BUILD_CONFIGURATION=Release +RUN dotnet publish "./DysonNetwork.Gateway.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false + +FROM base AS final +WORKDIR /app +COPY --from=publish /app/publish . +ENTRYPOINT ["dotnet", "DysonNetwork.Gateway.dll"] \ No newline at end of file diff --git a/DysonNetwork.Gateway/DysonNetwork.Gateway.csproj b/DysonNetwork.Gateway/DysonNetwork.Gateway.csproj new file mode 100644 index 0000000..1e7d39a --- /dev/null +++ b/DysonNetwork.Gateway/DysonNetwork.Gateway.csproj @@ -0,0 +1,19 @@ + + + + net9.0 + enable + enable + + + + + + + + + + + + + diff --git a/DysonNetwork.Gateway/EtcdProxyConfigProvider.cs b/DysonNetwork.Gateway/EtcdProxyConfigProvider.cs new file mode 100644 index 0000000..5984aea --- /dev/null +++ b/DysonNetwork.Gateway/EtcdProxyConfigProvider.cs @@ -0,0 +1,108 @@ +using System.Text; +using dotnet_etcd.interfaces; +using Yarp.ReverseProxy.Configuration; + +namespace DysonNetwork.Gateway; + +public class EtcdProxyConfigProvider : IProxyConfigProvider, IDisposable +{ + private readonly IEtcdClient _etcdClient; + private readonly IConfiguration _configuration; + private readonly ILogger _logger; + private readonly CancellationTokenSource _watchCts = new(); + private CancellationTokenSource _cts = new(); + + public EtcdProxyConfigProvider(IEtcdClient etcdClient, IConfiguration configuration, ILogger logger) + { + _etcdClient = etcdClient; + _configuration = configuration; + _logger = logger; + + // Watch for changes in etcd + _etcdClient.WatchRange("/services/", _ => + { + _logger.LogInformation("Etcd configuration changed. Reloading proxy config."); + _cts.Cancel(); + _cts = new CancellationTokenSource(); + }, cancellationToken: _watchCts.Token); + } + + public IProxyConfig GetConfig() + { + // This will be called by YARP when it needs a new config + _logger.LogInformation("Generating new proxy config."); + var response = _etcdClient.GetRange("/services/"); + var kvs = response.Kvs; + + var clusters = new List(); + var routes = new List(); + + var domainMappings = _configuration.GetSection("DomainMappings").GetChildren() + .ToDictionary(x => x.Key, x => x.Value); + + _logger.LogInformation("Indexing {ServiceCount} services from Etcd.", kvs.Count); + + foreach (var kv in kvs) + { + var serviceName = Encoding.UTF8.GetString(kv.Key.ToByteArray()).Replace("/services/", ""); + var serviceUrl = Encoding.UTF8.GetString(kv.Value.ToByteArray()); + + _logger.LogInformation(" Service: {ServiceName}, URL: {ServiceUrl}", serviceName, serviceUrl); + + var cluster = new ClusterConfig + { + ClusterId = serviceName, + Destinations = new Dictionary + { + { "destination1", new DestinationConfig { Address = serviceUrl } } + } + }; + clusters.Add(cluster); + + // Host-based routing + if (domainMappings.TryGetValue(serviceName, out var domain)) + { + var hostRoute = new RouteConfig + { + RouteId = $"{serviceName}-host", + ClusterId = serviceName, + Match = new RouteMatch + { + Hosts = new[] { domain }, + Path = "/{**catch-all}" + } + }; + routes.Add(hostRoute); + _logger.LogInformation(" Added Host-based Route: {Host}", domain); + } + + // Path-based routing + var pathRoute = new RouteConfig + { + RouteId = $"{serviceName}-path", + ClusterId = serviceName, + Match = new RouteMatch { Path = $"/{serviceName}/{{**catch-all}}" } + }; + routes.Add(pathRoute); + _logger.LogInformation(" Added Path-based Route: {Path}", pathRoute.Match.Path); + } + + return new CustomProxyConfig(routes, clusters); + } + + private class CustomProxyConfig(IReadOnlyList routes, IReadOnlyList clusters) + : IProxyConfig + { + public IReadOnlyList Routes { get; } = routes; + public IReadOnlyList Clusters { get; } = clusters; + public Microsoft.Extensions.Primitives.IChangeToken ChangeToken { get; } = new Microsoft.Extensions.Primitives.CancellationChangeToken(CancellationToken.None); + } + + public void Dispose() + { + _cts.Cancel(); + _cts.Dispose(); + _watchCts.Cancel(); + _watchCts.Dispose(); + } +} \ No newline at end of file diff --git a/DysonNetwork.Gateway/Program.cs b/DysonNetwork.Gateway/Program.cs new file mode 100644 index 0000000..df34149 --- /dev/null +++ b/DysonNetwork.Gateway/Program.cs @@ -0,0 +1,15 @@ +using DysonNetwork.Gateway.Startup; + +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. +builder.Services.AddGateway(builder.Configuration); +builder.Services.AddControllers(); + +var app = builder.Build(); + +// app.UseHttpsRedirection(); + +app.MapReverseProxy(); + +app.Run(); diff --git a/DysonNetwork.Gateway/Properties/launchSettings.json b/DysonNetwork.Gateway/Properties/launchSettings.json new file mode 100644 index 0000000..bb5bb2e --- /dev/null +++ b/DysonNetwork.Gateway/Properties/launchSettings.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "http://localhost:5094", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "https://localhost:7034;http://localhost:5094", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/DysonNetwork.Gateway/Startup/ServiceCollectionExtensions.cs b/DysonNetwork.Gateway/Startup/ServiceCollectionExtensions.cs new file mode 100644 index 0000000..ba1f598 --- /dev/null +++ b/DysonNetwork.Gateway/Startup/ServiceCollectionExtensions.cs @@ -0,0 +1,16 @@ +using DysonNetwork.Shared.Registry; +using Yarp.ReverseProxy.Configuration; + +namespace DysonNetwork.Gateway.Startup; + +public static class ServiceCollectionExtensions +{ + public static IServiceCollection AddGateway(this IServiceCollection services, IConfiguration configuration) + { + services.AddReverseProxy(); + services.AddRegistryService(configuration); + services.AddSingleton(); + + return services; + } +} diff --git a/DysonNetwork.Gateway/appsettings.json b/DysonNetwork.Gateway/appsettings.json new file mode 100644 index 0000000..0e7c15b --- /dev/null +++ b/DysonNetwork.Gateway/appsettings.json @@ -0,0 +1,25 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*", + "ConnectionStrings": { + "Etcd": "etcd.orb.local:2379" + }, + "Etcd": { + "Insecure": true + }, + "Service": { + "Name": "DysonNetwork.Gateway", + "Url": "https://localhost:7034" + }, + "DomainMappings": { + "DysonNetwork.Pass": "id.solsynth.dev", + "DysonNetwork.Drive": "drive.solsynth.dev", + "DysonNetwork.Pusher": "push.solsynth.dev", + "DysonNetwork.Sphere": "sphere.solsynth.dev" + } +} diff --git a/DysonNetwork.Shared/Auth/Startup.cs b/DysonNetwork.Shared/Auth/Startup.cs index 103aac2..3cd02ed 100644 --- a/DysonNetwork.Shared/Auth/Startup.cs +++ b/DysonNetwork.Shared/Auth/Startup.cs @@ -15,8 +15,8 @@ public static class DysonAuthStartup { var etcdClient = sp.GetRequiredService(); var config = sp.GetRequiredService(); - var clientCertPath = config["Service:ClientCert"]; - var clientKeyPath = config["Service:ClientKey"]; + var clientCertPath = config["Service:ClientCert"]!; + var clientKeyPath = config["Service:ClientKey"]!; var clientCertPassword = config["Service:CertPassword"]; return GrpcClientHelper @@ -24,6 +24,20 @@ public static class DysonAuthStartup .GetAwaiter() .GetResult(); }); + + services.AddSingleton(sp => + { + var etcdClient = sp.GetRequiredService(); + var config = sp.GetRequiredService(); + var clientCertPath = config["Service:ClientCert"]!; + var clientKeyPath = config["Service:ClientKey"]!; + var clientCertPassword = config["Service:CertPassword"]; + + return GrpcClientHelper + .CreatePermissionServiceClient(etcdClient, clientCertPath, clientKeyPath, clientCertPassword) + .GetAwaiter() + .GetResult(); + }); services.AddAuthentication(options => { diff --git a/DysonNetwork.Shared/Proto/GrpcClientHelper.cs b/DysonNetwork.Shared/Proto/GrpcClientHelper.cs index f254575..05b3213 100644 --- a/DysonNetwork.Shared/Proto/GrpcClientHelper.cs +++ b/DysonNetwork.Shared/Proto/GrpcClientHelper.cs @@ -61,6 +61,18 @@ public static class GrpcClientHelper return new AuthService.AuthServiceClient(CreateCallInvoker(url, clientCertPath, clientKeyPath, clientCertPassword)); } + + public static async Task CreatePermissionServiceClient( + IEtcdClient etcdClient, + string clientCertPath, + string clientKeyPath, + string? clientCertPassword = null + ) + { + var url = await GetServiceUrlFromEtcd(etcdClient, "DysonNetwork.Pass"); + return new PermissionService.PermissionServiceClient(CreateCallInvoker(url, clientCertPath, clientKeyPath, + clientCertPassword)); + } public static async Task CreatePusherServiceClient( IEtcdClient etcdClient, diff --git a/DysonNetwork.sln b/DysonNetwork.sln index 1ef9fd7..0ce0170 100644 --- a/DysonNetwork.sln +++ b/DysonNetwork.sln @@ -1,5 +1,6 @@  Microsoft Visual Studio Solution File, Format Version 12.00 +# Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DysonNetwork.Sphere", "DysonNetwork.Sphere\DysonNetwork.Sphere.csproj", "{CFF62EFA-F4C2-4FC7-8D97-25570B4DB452}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{A444D180-5B51-49C3-A35D-AA55832BBC66}" @@ -15,6 +16,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DysonNetwork.Pusher", "Dyso EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DysonNetwork.Drive", "DysonNetwork.Drive\DysonNetwork.Drive.csproj", "{8DE0B783-8852-494D-B90A-201ABBB71202}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DysonNetwork.Gateway", "DysonNetwork.Gateway\DysonNetwork.Gateway.csproj", "{19EB0086-4049-4B78-91C4-EAC37130A006}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -41,5 +44,9 @@ Global {8DE0B783-8852-494D-B90A-201ABBB71202}.Debug|Any CPU.Build.0 = Debug|Any CPU {8DE0B783-8852-494D-B90A-201ABBB71202}.Release|Any CPU.ActiveCfg = Release|Any CPU {8DE0B783-8852-494D-B90A-201ABBB71202}.Release|Any CPU.Build.0 = Release|Any CPU + {19EB0086-4049-4B78-91C4-EAC37130A006}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {19EB0086-4049-4B78-91C4-EAC37130A006}.Debug|Any CPU.Build.0 = Debug|Any CPU + {19EB0086-4049-4B78-91C4-EAC37130A006}.Release|Any CPU.ActiveCfg = Release|Any CPU + {19EB0086-4049-4B78-91C4-EAC37130A006}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/compose.yaml b/compose.yaml index 00ec211..fbfb60d 100644 --- a/compose.yaml +++ b/compose.yaml @@ -1,7 +1,96 @@ -services: - sphere: - image: xsheep2010/dyson-sphere:latest +services: + etcd: + image: bitnami/etcd:latest + ports: + - "2379:2379" + - "2380:2380" + environment: + - ETCD_ADVERTISE_CLIENT_URLS=http://etcd:2379 + - ETCD_LISTEN_CLIENT_URLS=http://0.0.0.0:2379 + - ETCD_LISTEN_PEER_URLS=http://0.0.0.0:2380 + - ETCD_INITIAL_ADVERTISE_PEER_URLS=http://etcd:2380 + - ETCD_INITIAL_CLUSTER_TOKEN=etcd-cluster + - ETCD_INITIAL_CLUSTER_STATE=new + - ETCD_INITIAL_CLUSTER=etcd=http://etcd:2380 + healthcheck: + test: ["CMD", "etcdctl", "get", "/health"] + interval: 5s + timeout: 5s + retries: 5 + + gateway: + build: + context: . + dockerfile: DysonNetwork.Gateway/Dockerfile + ports: + - "8000:8080" + environment: + - ConnectionStrings__Etcd=http://etcd:2379 + - Etcd__Insecure=true + - Service__Name=DysonNetwork.Gateway + - Service__Url=http://gateway:8080 + depends_on: + etcd: + condition: service_healthy + + drive: + build: + context: . + dockerfile: DysonNetwork.Drive/Dockerfile ports: - "8001:8080" + environment: + - ConnectionStrings__Etcd=http://etcd:2379 + - Etcd__Insecure=true + - Service__Name=DysonNetwork.Drive + - Service__Url=http://drive:8080 + depends_on: + etcd: + condition: service_healthy + + pass: + build: + context: . + dockerfile: DysonNetwork.Pass/Dockerfile + ports: + - "8002:8080" + environment: + - ConnectionStrings__Etcd=http://etcd:2379 + - Etcd__Insecure=true + - Service__Name=DysonNetwork.Pass + - Service__Url=http://pass:8080 + depends_on: + etcd: + condition: service_healthy + + pusher: + build: + context: . + dockerfile: DysonNetwork.Pusher/Dockerfile + ports: + - "8003:8080" + environment: + - ConnectionStrings__Etcd=http://etcd:2379 + - Etcd__Insecure=true + - Service__Name=DysonNetwork.Pusher + - Service__Url=http://pusher:8080 + depends_on: + etcd: + condition: service_healthy + + sphere: + build: + context: . + dockerfile: DysonNetwork.Sphere/Dockerfile + ports: + - "8004:8080" + environment: + - ConnectionStrings__Etcd=http://etcd:2379 + - Etcd__Insecure=true + - Service__Name=DysonNetwork.Sphere + - Service__Url=http://sphere:8080 volumes: - "./keys:/app/keys" + depends_on: + etcd: + condition: service_healthy \ No newline at end of file From 3310487aba610da53ae58f9890cf2a9460e329b2 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Tue, 15 Jul 2025 19:22:31 +0800 Subject: [PATCH 22/42] :sparkles: A well-done gateway --- .../Controllers/WellKnownController.cs | 33 ++++++- DysonNetwork.Gateway/Program.cs | 3 +- ...ider.cs => RegistryProxyConfigProvider.cs} | 86 +++++++++++++++++-- .../Startup/ServiceCollectionExtensions.cs | 2 +- DysonNetwork.Gateway/appsettings.json | 20 ++++- 5 files changed, 130 insertions(+), 14 deletions(-) rename DysonNetwork.Gateway/{EtcdProxyConfigProvider.cs => RegistryProxyConfigProvider.cs} (51%) diff --git a/DysonNetwork.Gateway/Controllers/WellKnownController.cs b/DysonNetwork.Gateway/Controllers/WellKnownController.cs index 7e94544..811ec68 100644 --- a/DysonNetwork.Gateway/Controllers/WellKnownController.cs +++ b/DysonNetwork.Gateway/Controllers/WellKnownController.cs @@ -1,10 +1,12 @@ using Microsoft.AspNetCore.Mvc; +using Yarp.ReverseProxy.Configuration; namespace DysonNetwork.Gateway.Controllers; [ApiController] [Route("/.well-known")] -public class WellKnownController(IConfiguration configuration) : ControllerBase +public class WellKnownController(IConfiguration configuration, IProxyConfigProvider proxyConfigProvider) + : ControllerBase { [HttpGet("domains")] public IActionResult GetDomainMappings() @@ -13,4 +15,33 @@ public class WellKnownController(IConfiguration configuration) : ControllerBase .ToDictionary(x => x.Key, x => x.Value); return Ok(domainMappings); } + + [HttpGet("routes")] + public IActionResult GetProxyRules() + { + var config = proxyConfigProvider.GetConfig(); + var rules = config.Routes.Select(r => new + { + r.RouteId, + r.ClusterId, + Match = new + { + r.Match.Path, + Hosts = r.Match.Hosts != null ? string.Join(", ", r.Match.Hosts) : null + }, + Transforms = r.Transforms?.Select(t => t.Select(kv => $"{kv.Key}: {kv.Value}").ToList()) + }).ToList(); + + var clusters = config.Clusters.Select(c => new + { + c.ClusterId, + Destinations = c.Destinations?.Select(d => new + { + d.Key, + d.Value.Address + }).ToList() + }).ToList(); + + return Ok(new { Rules = rules, Clusters = clusters }); + } } \ No newline at end of file diff --git a/DysonNetwork.Gateway/Program.cs b/DysonNetwork.Gateway/Program.cs index df34149..3b97bbb 100644 --- a/DysonNetwork.Gateway/Program.cs +++ b/DysonNetwork.Gateway/Program.cs @@ -8,8 +8,7 @@ builder.Services.AddControllers(); var app = builder.Build(); -// app.UseHttpsRedirection(); - +app.MapControllers(); app.MapReverseProxy(); app.Run(); diff --git a/DysonNetwork.Gateway/EtcdProxyConfigProvider.cs b/DysonNetwork.Gateway/RegistryProxyConfigProvider.cs similarity index 51% rename from DysonNetwork.Gateway/EtcdProxyConfigProvider.cs rename to DysonNetwork.Gateway/RegistryProxyConfigProvider.cs index 5984aea..f816ca6 100644 --- a/DysonNetwork.Gateway/EtcdProxyConfigProvider.cs +++ b/DysonNetwork.Gateway/RegistryProxyConfigProvider.cs @@ -4,15 +4,15 @@ using Yarp.ReverseProxy.Configuration; namespace DysonNetwork.Gateway; -public class EtcdProxyConfigProvider : IProxyConfigProvider, IDisposable +public class RegistryProxyConfigProvider : IProxyConfigProvider, IDisposable { private readonly IEtcdClient _etcdClient; private readonly IConfiguration _configuration; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly CancellationTokenSource _watchCts = new(); private CancellationTokenSource _cts = new(); - public EtcdProxyConfigProvider(IEtcdClient etcdClient, IConfiguration configuration, ILogger logger) + public RegistryProxyConfigProvider(IEtcdClient etcdClient, IConfiguration configuration, ILogger logger) { _etcdClient = etcdClient; _configuration = configuration; @@ -34,20 +34,78 @@ public class EtcdProxyConfigProvider : IProxyConfigProvider, IDisposable var response = _etcdClient.GetRange("/services/"); var kvs = response.Kvs; + var serviceMap = kvs.ToDictionary( + kv => Encoding.UTF8.GetString(kv.Key.ToByteArray()).Replace("/services/", ""), + kv => Encoding.UTF8.GetString(kv.Value.ToByteArray()) + ); + var clusters = new List(); var routes = new List(); var domainMappings = _configuration.GetSection("DomainMappings").GetChildren() .ToDictionary(x => x.Key, x => x.Value); + var pathAliases = _configuration.GetSection("PathAliases").GetChildren() + .ToDictionary(x => x.Key, x => x.Value); + + var directRoutes = _configuration.GetSection("DirectRoutes").Get>() ?? new List(); + _logger.LogInformation("Indexing {ServiceCount} services from Etcd.", kvs.Count); - foreach (var kv in kvs) - { - var serviceName = Encoding.UTF8.GetString(kv.Key.ToByteArray()).Replace("/services/", ""); - var serviceUrl = Encoding.UTF8.GetString(kv.Value.ToByteArray()); + var gatewayServiceName = _configuration["Service:Name"]; - _logger.LogInformation(" Service: {ServiceName}, URL: {ServiceUrl}", serviceName, serviceUrl); + // Add direct routes + foreach (var directRoute in directRoutes) + { + if (serviceMap.TryGetValue(directRoute.Service, out var serviceUrl)) + { + var cluster = new ClusterConfig + { + ClusterId = directRoute.Service, + Destinations = new Dictionary + { + { "destination1", new DestinationConfig { Address = serviceUrl } } + } + }; + clusters.Add(cluster); + + var route = new RouteConfig + { + RouteId = $"direct-{directRoute.Service}-{directRoute.Path.Replace("/", "-")}", + ClusterId = directRoute.Service, + Match = new RouteMatch { Path = directRoute.Path } + }; + routes.Add(route); + _logger.LogInformation(" Added Direct Route: {Path} -> {Service}", directRoute.Path, directRoute.Service); + } + else + { + _logger.LogWarning(" Direct route service {Service} not found in Etcd.", directRoute.Service); + } + } + + foreach (var serviceName in serviceMap.Keys) + { + if (serviceName == gatewayServiceName) + { + _logger.LogInformation("Skipping gateway service: {ServiceName}", serviceName); + continue; + } + + var serviceUrl = serviceMap[serviceName]; + + // Determine the path alias + string pathAlias; + if (pathAliases.TryGetValue(serviceName, out var alias)) + { + pathAlias = alias; + } + else + { + pathAlias = serviceName.Split('.').Last().ToLowerInvariant(); + } + + _logger.LogInformation(" Service: {ServiceName}, URL: {ServiceUrl}, Path Alias: {PathAlias}", serviceName, serviceUrl, pathAlias); var cluster = new ClusterConfig { @@ -81,7 +139,11 @@ public class EtcdProxyConfigProvider : IProxyConfigProvider, IDisposable { RouteId = $"{serviceName}-path", ClusterId = serviceName, - Match = new RouteMatch { Path = $"/{serviceName}/{{**catch-all}}" } + Match = new RouteMatch { Path = $"/{pathAlias}/{{**catch-all}}" }, + Transforms = new List> + { + new Dictionary { { "PathRemovePrefix", $"/{pathAlias}" } } + } }; routes.Add(pathRoute); _logger.LogInformation(" Added Path-based Route: {Path}", pathRoute.Match.Path); @@ -98,6 +160,12 @@ public class EtcdProxyConfigProvider : IProxyConfigProvider, IDisposable public Microsoft.Extensions.Primitives.IChangeToken ChangeToken { get; } = new Microsoft.Extensions.Primitives.CancellationChangeToken(CancellationToken.None); } + private class DirectRouteConfig + { + public string Path { get; set; } + public string Service { get; set; } + } + public void Dispose() { _cts.Cancel(); diff --git a/DysonNetwork.Gateway/Startup/ServiceCollectionExtensions.cs b/DysonNetwork.Gateway/Startup/ServiceCollectionExtensions.cs index ba1f598..28a2153 100644 --- a/DysonNetwork.Gateway/Startup/ServiceCollectionExtensions.cs +++ b/DysonNetwork.Gateway/Startup/ServiceCollectionExtensions.cs @@ -9,7 +9,7 @@ public static class ServiceCollectionExtensions { services.AddReverseProxy(); services.AddRegistryService(configuration); - services.AddSingleton(); + services.AddSingleton(); return services; } diff --git a/DysonNetwork.Gateway/appsettings.json b/DysonNetwork.Gateway/appsettings.json index 0e7c15b..6d0486f 100644 --- a/DysonNetwork.Gateway/appsettings.json +++ b/DysonNetwork.Gateway/appsettings.json @@ -21,5 +21,23 @@ "DysonNetwork.Drive": "drive.solsynth.dev", "DysonNetwork.Pusher": "push.solsynth.dev", "DysonNetwork.Sphere": "sphere.solsynth.dev" - } + }, + "PathAliases": { + "DysonNetwork.Pass": "id", + "DysonNetwork.Drive": "drive" + }, + "DirectRoutes": [ + { + "Path": "/ws", + "Service": "DysonNetwork.Pusher" + }, + { + "Path": "/.well-known/openid-configuration", + "Service": "DysonNetwork.Pass" + }, + { + "Path": "/.well-known/jwks", + "Service": "DysonNetwork.Pass" + } + ] } From 5549051ec5ccf17fadb94aacfa516b57ecd1eddd Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Tue, 15 Jul 2025 20:43:44 +0800 Subject: [PATCH 23/42] :bug: Fixes on gateway multiple services case --- .../RegistryProxyConfigProvider.cs | 102 ++++++++++++------ 1 file changed, 69 insertions(+), 33 deletions(-) diff --git a/DysonNetwork.Gateway/RegistryProxyConfigProvider.cs b/DysonNetwork.Gateway/RegistryProxyConfigProvider.cs index f816ca6..0b1f952 100644 --- a/DysonNetwork.Gateway/RegistryProxyConfigProvider.cs +++ b/DysonNetwork.Gateway/RegistryProxyConfigProvider.cs @@ -12,7 +12,8 @@ public class RegistryProxyConfigProvider : IProxyConfigProvider, IDisposable private readonly CancellationTokenSource _watchCts = new(); private CancellationTokenSource _cts = new(); - public RegistryProxyConfigProvider(IEtcdClient etcdClient, IConfiguration configuration, ILogger logger) + public RegistryProxyConfigProvider(IEtcdClient etcdClient, IConfiguration configuration, + ILogger logger) { _etcdClient = etcdClient; _configuration = configuration; @@ -48,7 +49,8 @@ public class RegistryProxyConfigProvider : IProxyConfigProvider, IDisposable var pathAliases = _configuration.GetSection("PathAliases").GetChildren() .ToDictionary(x => x.Key, x => x.Value); - var directRoutes = _configuration.GetSection("DirectRoutes").Get>() ?? new List(); + var directRoutes = _configuration.GetSection("DirectRoutes").Get>() ?? + []; _logger.LogInformation("Indexing {ServiceCount} services from Etcd.", kvs.Count); @@ -59,15 +61,19 @@ public class RegistryProxyConfigProvider : IProxyConfigProvider, IDisposable { if (serviceMap.TryGetValue(directRoute.Service, out var serviceUrl)) { - var cluster = new ClusterConfig + var existingCluster = clusters.FirstOrDefault(c => c.ClusterId == directRoute.Service); + if (existingCluster is null) { - ClusterId = directRoute.Service, - Destinations = new Dictionary + var cluster = new ClusterConfig { - { "destination1", new DestinationConfig { Address = serviceUrl } } - } - }; - clusters.Add(cluster); + ClusterId = directRoute.Service, + Destinations = new Dictionary + { + { "destination1", new DestinationConfig { Address = serviceUrl } } + } + }; + clusters.Add(cluster); + } var route = new RouteConfig { @@ -76,7 +82,8 @@ public class RegistryProxyConfigProvider : IProxyConfigProvider, IDisposable Match = new RouteMatch { Path = directRoute.Path } }; routes.Add(route); - _logger.LogInformation(" Added Direct Route: {Path} -> {Service}", directRoute.Path, directRoute.Service); + _logger.LogInformation(" Added Direct Route: {Path} -> {Service}", directRoute.Path, + directRoute.Service); } else { @@ -95,27 +102,53 @@ public class RegistryProxyConfigProvider : IProxyConfigProvider, IDisposable var serviceUrl = serviceMap[serviceName]; // Determine the path alias - string pathAlias; - if (pathAliases.TryGetValue(serviceName, out var alias)) + string? pathAlias; + pathAlias = pathAliases.TryGetValue(serviceName, out var alias) + ? alias + : serviceName.Split('.').Last().ToLowerInvariant(); + + _logger.LogInformation(" Service: {ServiceName}, URL: {ServiceUrl}, Path Alias: {PathAlias}", serviceName, + serviceUrl, pathAlias); + + // Check if the cluster already exists + var existingCluster = clusters.FirstOrDefault(c => c.ClusterId == serviceName); + if (existingCluster == null) { - pathAlias = alias; + var cluster = new ClusterConfig + { + ClusterId = serviceName, + Destinations = new Dictionary + { + { "destination1", new DestinationConfig { Address = serviceUrl } } + } + }; + clusters.Add(cluster); + _logger.LogInformation(" Added Cluster: {ServiceName}", serviceName); } else { - pathAlias = serviceName.Split('.').Last().ToLowerInvariant(); - } - - _logger.LogInformation(" Service: {ServiceName}, URL: {ServiceUrl}, Path Alias: {PathAlias}", serviceName, serviceUrl, pathAlias); - - var cluster = new ClusterConfig - { - ClusterId = serviceName, - Destinations = new Dictionary + // Create a new cluster with merged destinations + var newDestinations = new Dictionary(existingCluster.Destinations) { - { "destination1", new DestinationConfig { Address = serviceUrl } } - } - }; - clusters.Add(cluster); + { + $"destination{existingCluster.Destinations.Count + 1}", + new DestinationConfig { Address = serviceUrl } + } + }; + + var mergedCluster = new ClusterConfig + { + ClusterId = serviceName, + Destinations = newDestinations + }; + + // Replace the existing cluster with the merged one + var index = clusters.IndexOf(existingCluster); + clusters[index] = mergedCluster; + + _logger.LogInformation(" Updated Cluster {ServiceName} with {DestinationCount} destinations", + serviceName, mergedCluster.Destinations.Count); + } // Host-based routing if (domainMappings.TryGetValue(serviceName, out var domain)) @@ -126,7 +159,7 @@ public class RegistryProxyConfigProvider : IProxyConfigProvider, IDisposable ClusterId = serviceName, Match = new RouteMatch { - Hosts = new[] { domain }, + Hosts = [domain], Path = "/{**catch-all}" } }; @@ -142,7 +175,8 @@ public class RegistryProxyConfigProvider : IProxyConfigProvider, IDisposable Match = new RouteMatch { Path = $"/{pathAlias}/{{**catch-all}}" }, Transforms = new List> { - new Dictionary { { "PathRemovePrefix", $"/{pathAlias}" } } + new() { { "PathRemovePrefix", $"/{pathAlias}" } }, + new() { { "PathPrefix", "/api" } } } }; routes.Add(pathRoute); @@ -157,16 +191,18 @@ public class RegistryProxyConfigProvider : IProxyConfigProvider, IDisposable { public IReadOnlyList Routes { get; } = routes; public IReadOnlyList Clusters { get; } = clusters; - public Microsoft.Extensions.Primitives.IChangeToken ChangeToken { get; } = new Microsoft.Extensions.Primitives.CancellationChangeToken(CancellationToken.None); + + public Microsoft.Extensions.Primitives.IChangeToken ChangeToken { get; } = + new Microsoft.Extensions.Primitives.CancellationChangeToken(CancellationToken.None); } - private class DirectRouteConfig + private record DirectRouteConfig { - public string Path { get; set; } - public string Service { get; set; } + public required string Path { get; set; } + public required string Service { get; set; } } - public void Dispose() + public virtual void Dispose() { _cts.Cancel(); _cts.Dispose(); From cd4af2e26f1f51f968b9a7f4dde34206d787d718 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Wed, 16 Jul 2025 01:53:00 +0800 Subject: [PATCH 24/42] :tada: Setup Pass frontend project --- DysonNetwork.Drive/DysonNetwork.Drive.csproj | 4 + DysonNetwork.Drive/VersionController.cs | 20 + DysonNetwork.Drive/version.json | 7 + .../DysonNetwork.Gateway.csproj | 4 + .../RegistryProxyConfigProvider.cs | 2 +- DysonNetwork.Gateway/VersionController.cs | 20 + DysonNetwork.Gateway/version.json | 7 + DysonNetwork.Pass/.gitignore | 1 + DysonNetwork.Pass/Client/.editorconfig | 9 + DysonNetwork.Pass/Client/.gitattributes | 1 + DysonNetwork.Pass/Client/.gitignore | 30 + DysonNetwork.Pass/Client/.prettierrc.json | 6 + .../Client/.vscode/extensions.json | 9 + DysonNetwork.Pass/Client/README.md | 33 + DysonNetwork.Pass/Client/bun.lock | 886 ++++ DysonNetwork.Pass/Client/env.d.ts | 1 + DysonNetwork.Pass/Client/eslint.config.ts | 29 + DysonNetwork.Pass/Client/index.html | 13 + DysonNetwork.Pass/Client/package-lock.json | 4523 +++++++++++++++++ DysonNetwork.Pass/Client/package.json | 48 + DysonNetwork.Pass/Client/public/favicon.ico | Bin 0 -> 4286 bytes DysonNetwork.Pass/Client/src/assets/main.css | 9 + .../Client/src/layouts/default.vue | 25 + DysonNetwork.Pass/Client/src/main.ts | 16 + DysonNetwork.Pass/Client/src/root.vue | 15 + DysonNetwork.Pass/Client/src/router/index.ts | 14 + DysonNetwork.Pass/Client/src/views/index.vue | 37 + DysonNetwork.Pass/Client/tsconfig.app.json | 12 + DysonNetwork.Pass/Client/tsconfig.json | 11 + DysonNetwork.Pass/Client/tsconfig.node.json | 19 + DysonNetwork.Pass/Client/vite.config.ts | 37 + DysonNetwork.Pass/DysonNetwork.Pass.csproj | 163 +- .../Pages/Checkpoint/CheckpointPage.cshtml | 110 - .../Pages/Checkpoint/CheckpointPage.cshtml.cs | 14 - DysonNetwork.Pass/Pages/Shared/_Layout.cshtml | 62 - .../Pages/Spell/MagicSpellPage.cshtml | 91 - .../Pages/Spell/MagicSpellPage.cshtml.cs | 52 - DysonNetwork.Pass/Pages/_ViewImports.cshtml | 2 - DysonNetwork.Pass/Pages/_ViewStart.cshtml | 3 - DysonNetwork.Pass/Program.cs | 3 +- .../Startup/ApplicationConfiguration.cs | 19 +- DysonNetwork.Pass/VersionController.cs | 20 + DysonNetwork.Pass/version.json | 7 + .../DysonNetwork.Pusher.csproj | 4 + DysonNetwork.Shared/Data/AppVersion.cs | 8 + .../DysonNetwork.Sphere.csproj | 4 + .../Shared/_ValidationScriptsPartial.cshtml | 3 - DysonNetwork.Sphere/Pages/_ViewImports.cshtml | 3 +- DysonNetwork.Sphere/VersionController.cs | 20 + DysonNetwork.Sphere/version.json | 7 + DysonNetwork.sln.DotSettings.user | 1 + 51 files changed, 6033 insertions(+), 411 deletions(-) create mode 100644 DysonNetwork.Drive/VersionController.cs create mode 100644 DysonNetwork.Drive/version.json create mode 100644 DysonNetwork.Gateway/VersionController.cs create mode 100644 DysonNetwork.Gateway/version.json create mode 100644 DysonNetwork.Pass/.gitignore create mode 100644 DysonNetwork.Pass/Client/.editorconfig create mode 100644 DysonNetwork.Pass/Client/.gitattributes create mode 100644 DysonNetwork.Pass/Client/.gitignore create mode 100644 DysonNetwork.Pass/Client/.prettierrc.json create mode 100644 DysonNetwork.Pass/Client/.vscode/extensions.json create mode 100644 DysonNetwork.Pass/Client/README.md create mode 100644 DysonNetwork.Pass/Client/bun.lock create mode 100644 DysonNetwork.Pass/Client/env.d.ts create mode 100644 DysonNetwork.Pass/Client/eslint.config.ts create mode 100644 DysonNetwork.Pass/Client/index.html create mode 100644 DysonNetwork.Pass/Client/package-lock.json create mode 100644 DysonNetwork.Pass/Client/package.json create mode 100644 DysonNetwork.Pass/Client/public/favicon.ico create mode 100644 DysonNetwork.Pass/Client/src/assets/main.css create mode 100644 DysonNetwork.Pass/Client/src/layouts/default.vue create mode 100644 DysonNetwork.Pass/Client/src/main.ts create mode 100644 DysonNetwork.Pass/Client/src/root.vue create mode 100644 DysonNetwork.Pass/Client/src/router/index.ts create mode 100644 DysonNetwork.Pass/Client/src/views/index.vue create mode 100644 DysonNetwork.Pass/Client/tsconfig.app.json create mode 100644 DysonNetwork.Pass/Client/tsconfig.json create mode 100644 DysonNetwork.Pass/Client/tsconfig.node.json create mode 100644 DysonNetwork.Pass/Client/vite.config.ts delete mode 100644 DysonNetwork.Pass/Pages/Checkpoint/CheckpointPage.cshtml delete mode 100644 DysonNetwork.Pass/Pages/Checkpoint/CheckpointPage.cshtml.cs delete mode 100644 DysonNetwork.Pass/Pages/Shared/_Layout.cshtml delete mode 100644 DysonNetwork.Pass/Pages/Spell/MagicSpellPage.cshtml delete mode 100644 DysonNetwork.Pass/Pages/Spell/MagicSpellPage.cshtml.cs delete mode 100644 DysonNetwork.Pass/Pages/_ViewImports.cshtml delete mode 100644 DysonNetwork.Pass/Pages/_ViewStart.cshtml create mode 100644 DysonNetwork.Pass/VersionController.cs create mode 100644 DysonNetwork.Pass/version.json create mode 100644 DysonNetwork.Shared/Data/AppVersion.cs delete mode 100644 DysonNetwork.Sphere/Pages/Shared/_ValidationScriptsPartial.cshtml create mode 100644 DysonNetwork.Sphere/VersionController.cs create mode 100644 DysonNetwork.Sphere/version.json diff --git a/DysonNetwork.Drive/DysonNetwork.Drive.csproj b/DysonNetwork.Drive/DysonNetwork.Drive.csproj index 0543986..1685fbd 100644 --- a/DysonNetwork.Drive/DysonNetwork.Drive.csproj +++ b/DysonNetwork.Drive/DysonNetwork.Drive.csproj @@ -21,6 +21,10 @@ runtime; build; native; contentfiles; analyzers; buildtransitive + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/DysonNetwork.Drive/VersionController.cs b/DysonNetwork.Drive/VersionController.cs new file mode 100644 index 0000000..f9f5251 --- /dev/null +++ b/DysonNetwork.Drive/VersionController.cs @@ -0,0 +1,20 @@ +using DysonNetwork.Shared.Data; +using Microsoft.AspNetCore.Mvc; + +namespace DysonNetwork.Drive; + +[ApiController] +[Route("/api/version")] +public class VersionController : ControllerBase +{ + [HttpGet] + public IActionResult Get() + { + return Ok(new AppVersion + { + Version = ThisAssembly.AssemblyVersion, + Commit = ThisAssembly.GitCommitId, + UpdateDate = ThisAssembly.GitCommitDate + }); + } +} diff --git a/DysonNetwork.Drive/version.json b/DysonNetwork.Drive/version.json new file mode 100644 index 0000000..9fbf8d3 --- /dev/null +++ b/DysonNetwork.Drive/version.json @@ -0,0 +1,7 @@ +{ + "version": "1.0", + "publicReleaseRefSpec": ["^refs/heads/main$"], + "cloudBuild": { + "setVersionVariables": true + } +} diff --git a/DysonNetwork.Gateway/DysonNetwork.Gateway.csproj b/DysonNetwork.Gateway/DysonNetwork.Gateway.csproj index 1e7d39a..7403f3c 100644 --- a/DysonNetwork.Gateway/DysonNetwork.Gateway.csproj +++ b/DysonNetwork.Gateway/DysonNetwork.Gateway.csproj @@ -9,6 +9,10 @@ + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/DysonNetwork.Gateway/RegistryProxyConfigProvider.cs b/DysonNetwork.Gateway/RegistryProxyConfigProvider.cs index 0b1f952..c7851d2 100644 --- a/DysonNetwork.Gateway/RegistryProxyConfigProvider.cs +++ b/DysonNetwork.Gateway/RegistryProxyConfigProvider.cs @@ -125,7 +125,7 @@ public class RegistryProxyConfigProvider : IProxyConfigProvider, IDisposable clusters.Add(cluster); _logger.LogInformation(" Added Cluster: {ServiceName}", serviceName); } - else + else if (existingCluster.Destinations is not null) { // Create a new cluster with merged destinations var newDestinations = new Dictionary(existingCluster.Destinations) diff --git a/DysonNetwork.Gateway/VersionController.cs b/DysonNetwork.Gateway/VersionController.cs new file mode 100644 index 0000000..6040eae --- /dev/null +++ b/DysonNetwork.Gateway/VersionController.cs @@ -0,0 +1,20 @@ +using DysonNetwork.Shared.Data; +using Microsoft.AspNetCore.Mvc; + +namespace DysonNetwork.Gateway; + +[ApiController] +[Route("/api/version")] +public class VersionController : ControllerBase +{ + [HttpGet] + public IActionResult Get() + { + return Ok(new AppVersion + { + Version = ThisAssembly.AssemblyVersion, + Commit = ThisAssembly.GitCommitId, + UpdateDate = ThisAssembly.GitCommitDate + }); + } +} diff --git a/DysonNetwork.Gateway/version.json b/DysonNetwork.Gateway/version.json new file mode 100644 index 0000000..9fbf8d3 --- /dev/null +++ b/DysonNetwork.Gateway/version.json @@ -0,0 +1,7 @@ +{ + "version": "1.0", + "publicReleaseRefSpec": ["^refs/heads/main$"], + "cloudBuild": { + "setVersionVariables": true + } +} diff --git a/DysonNetwork.Pass/.gitignore b/DysonNetwork.Pass/.gitignore new file mode 100644 index 0000000..d97800d --- /dev/null +++ b/DysonNetwork.Pass/.gitignore @@ -0,0 +1 @@ +/wwwroot/dist \ No newline at end of file diff --git a/DysonNetwork.Pass/Client/.editorconfig b/DysonNetwork.Pass/Client/.editorconfig new file mode 100644 index 0000000..5a5809d --- /dev/null +++ b/DysonNetwork.Pass/Client/.editorconfig @@ -0,0 +1,9 @@ +[*.{js,jsx,mjs,cjs,ts,tsx,mts,cts,vue,css,scss,sass,less,styl}] +charset = utf-8 +indent_size = 2 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true + +end_of_line = lf +max_line_length = 100 diff --git a/DysonNetwork.Pass/Client/.gitattributes b/DysonNetwork.Pass/Client/.gitattributes new file mode 100644 index 0000000..6313b56 --- /dev/null +++ b/DysonNetwork.Pass/Client/.gitattributes @@ -0,0 +1 @@ +* text=auto eol=lf diff --git a/DysonNetwork.Pass/Client/.gitignore b/DysonNetwork.Pass/Client/.gitignore new file mode 100644 index 0000000..8ee54e8 --- /dev/null +++ b/DysonNetwork.Pass/Client/.gitignore @@ -0,0 +1,30 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +.DS_Store +dist +dist-ssr +coverage +*.local + +/cypress/videos/ +/cypress/screenshots/ + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +*.tsbuildinfo diff --git a/DysonNetwork.Pass/Client/.prettierrc.json b/DysonNetwork.Pass/Client/.prettierrc.json new file mode 100644 index 0000000..29a2402 --- /dev/null +++ b/DysonNetwork.Pass/Client/.prettierrc.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://json.schemastore.org/prettierrc", + "semi": false, + "singleQuote": true, + "printWidth": 100 +} diff --git a/DysonNetwork.Pass/Client/.vscode/extensions.json b/DysonNetwork.Pass/Client/.vscode/extensions.json new file mode 100644 index 0000000..3f84126 --- /dev/null +++ b/DysonNetwork.Pass/Client/.vscode/extensions.json @@ -0,0 +1,9 @@ +{ + "recommendations": [ + "Vue.volar", + "dbaeumer.vscode-eslint", + "EditorConfig.EditorConfig", + "oxc.oxc-vscode", + "esbenp.prettier-vscode" + ] +} diff --git a/DysonNetwork.Pass/Client/README.md b/DysonNetwork.Pass/Client/README.md new file mode 100644 index 0000000..5a73e7a --- /dev/null +++ b/DysonNetwork.Pass/Client/README.md @@ -0,0 +1,33 @@ +# Web + +This template should help get you started developing with Vue 3 in Vite. + +## Recommended IDE Setup + +[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur). + +## Type Support for `.vue` Imports in TS + +TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) to make the TypeScript language service aware of `.vue` types. + +## Customize configuration + +See [Vite Configuration Reference](https://vite.dev/config/). + +## Project Setup + +```sh +bun install +``` + +### Compile and Hot-Reload for Development + +```sh +bun dev +``` + +### Type-Check, Compile and Minify for Production + +```sh +bun run build +``` diff --git a/DysonNetwork.Pass/Client/bun.lock b/DysonNetwork.Pass/Client/bun.lock new file mode 100644 index 0000000..f4ffe7d --- /dev/null +++ b/DysonNetwork.Pass/Client/bun.lock @@ -0,0 +1,886 @@ +{ + "lockfileVersion": 1, + "workspaces": { + "": { + "name": "@solar-network/pass", + "dependencies": { + "@fontsource-variable/nunito": "^5.2.6", + "@tailwindcss/vite": "^4.1.11", + "aspnet-prerendering": "^3.0.1", + "pinia": "^3.0.3", + "tailwindcss": "^4.1.11", + "vue": "^3.5.17", + "vue-router": "^4.5.1", + }, + "devDependencies": { + "@tsconfig/node22": "^22.0.2", + "@types/node": "^22.16.4", + "@vicons/material": "^0.13.0", + "@vitejs/plugin-vue": "^6.0.0", + "@vitejs/plugin-vue-jsx": "^5.0.1", + "@vue/eslint-config-prettier": "^10.2.0", + "@vue/eslint-config-typescript": "^14.6.0", + "@vue/tsconfig": "^0.7.0", + "eslint": "^9.31.0", + "eslint-plugin-oxlint": "~1.1.0", + "eslint-plugin-vue": "~10.2.0", + "jiti": "^2.4.2", + "naive-ui": "^2.42.0", + "npm-run-all2": "^8.0.4", + "oxlint": "~1.1.0", + "prettier": "3.5.3", + "typescript": "~5.8.3", + "vite": "npm:rolldown-vite@latest", + "vite-plugin-vue-devtools": "^7.7.7", + "vue-tsc": "^2.2.12", + }, + }, + }, + "packages": { + "@ampproject/remapping": ["@ampproject/remapping@2.3.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw=="], + + "@antfu/utils": ["@antfu/utils@0.7.10", "", {}, "sha512-+562v9k4aI80m1+VuMHehNJWLOFjBnXn3tdOitzD0il5b7smkSBal4+a3oKiQTbrwMmN/TBUMDvbdoWDehgOww=="], + + "@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="], + + "@babel/compat-data": ["@babel/compat-data@7.28.0", "", {}, "sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw=="], + + "@babel/core": ["@babel/core@7.28.0", "", { "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.0", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.27.3", "@babel/helpers": "^7.27.6", "@babel/parser": "^7.28.0", "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.0", "@babel/types": "^7.28.0", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ=="], + + "@babel/generator": ["@babel/generator@7.28.0", "", { "dependencies": { "@babel/parser": "^7.28.0", "@babel/types": "^7.28.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg=="], + + "@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" } }, "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg=="], + + "@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.27.2", "", { "dependencies": { "@babel/compat-data": "^7.27.2", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ=="], + + "@babel/helper-create-class-features-plugin": ["@babel/helper-create-class-features-plugin@7.27.1", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.1", "@babel/helper-member-expression-to-functions": "^7.27.1", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/helper-replace-supers": "^7.27.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", "@babel/traverse": "^7.27.1", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-QwGAmuvM17btKU5VqXfb+Giw4JcN0hjuufz3DYnpeVDvZLAObloM77bhMXiqry3Iio+Ai4phVRDwl6WU10+r5A=="], + + "@babel/helper-globals": ["@babel/helper-globals@7.28.0", "", {}, "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw=="], + + "@babel/helper-member-expression-to-functions": ["@babel/helper-member-expression-to-functions@7.27.1", "", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA=="], + + "@babel/helper-module-imports": ["@babel/helper-module-imports@7.27.1", "", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w=="], + + "@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.27.3", "", { "dependencies": { "@babel/helper-module-imports": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1", "@babel/traverse": "^7.27.3" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg=="], + + "@babel/helper-optimise-call-expression": ["@babel/helper-optimise-call-expression@7.27.1", "", { "dependencies": { "@babel/types": "^7.27.1" } }, "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw=="], + + "@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="], + + "@babel/helper-replace-supers": ["@babel/helper-replace-supers@7.27.1", "", { "dependencies": { "@babel/helper-member-expression-to-functions": "^7.27.1", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/traverse": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA=="], + + "@babel/helper-skip-transparent-expression-wrappers": ["@babel/helper-skip-transparent-expression-wrappers@7.27.1", "", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg=="], + + "@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], + + "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.27.1", "", {}, "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow=="], + + "@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="], + + "@babel/helpers": ["@babel/helpers@7.27.6", "", { "dependencies": { "@babel/template": "^7.27.2", "@babel/types": "^7.27.6" } }, "sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug=="], + + "@babel/parser": ["@babel/parser@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.0" }, "bin": "./bin/babel-parser.js" }, "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g=="], + + "@babel/plugin-proposal-decorators": ["@babel/plugin-proposal-decorators@7.28.0", "", { "dependencies": { "@babel/helper-create-class-features-plugin": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1", "@babel/plugin-syntax-decorators": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-zOiZqvANjWDUaUS9xMxbMcK/Zccztbe/6ikvUXaG9nsPH3w6qh5UaPGAnirI/WhIbZ8m3OHU0ReyPrknG+ZKeg=="], + + "@babel/plugin-syntax-decorators": ["@babel/plugin-syntax-decorators@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-YMq8Z87Lhl8EGkmb0MwYkt36QnxC+fzCgrl66ereamPlYToRpIk5nUjKUY3QKLWq8mwUB1BgbeXcTJhZOCDg5A=="], + + "@babel/plugin-syntax-import-attributes": ["@babel/plugin-syntax-import-attributes@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww=="], + + "@babel/plugin-syntax-import-meta": ["@babel/plugin-syntax-import-meta@7.10.4", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g=="], + + "@babel/plugin-syntax-jsx": ["@babel/plugin-syntax-jsx@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w=="], + + "@babel/plugin-syntax-typescript": ["@babel/plugin-syntax-typescript@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ=="], + + "@babel/plugin-transform-typescript": ["@babel/plugin-transform-typescript@7.28.0", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-create-class-features-plugin": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", "@babel/plugin-syntax-typescript": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-4AEiDEBPIZvLQaWlc9liCavE0xRM0dNca41WtBeM3jgFptfUOSG9z0uteLhq6+3rq+WB6jIvUwKDTpXEHPJ2Vg=="], + + "@babel/template": ["@babel/template@7.27.2", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.2", "@babel/types": "^7.27.1" } }, "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw=="], + + "@babel/traverse": ["@babel/traverse@7.28.0", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.0", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.0", "@babel/template": "^7.27.2", "@babel/types": "^7.28.0", "debug": "^4.3.1" } }, "sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg=="], + + "@babel/types": ["@babel/types@7.28.1", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-x0LvFTekgSX+83TI28Y9wYPUfzrnl2aT5+5QLnO6v7mSJYtEEevuDRN0F0uSHRk1G1IWZC43o00Y0xDDrpBGPQ=="], + + "@css-render/plugin-bem": ["@css-render/plugin-bem@0.15.14", "", { "peerDependencies": { "css-render": "~0.15.14" } }, "sha512-QK513CJ7yEQxm/P3EwsI+d+ha8kSOcjGvD6SevM41neEMxdULE+18iuQK6tEChAWMOQNQPLG/Rw3Khb69r5neg=="], + + "@css-render/vue3-ssr": ["@css-render/vue3-ssr@0.15.14", "", { "peerDependencies": { "vue": "^3.0.11" } }, "sha512-//8027GSbxE9n3QlD73xFY6z4ZbHbvrOVB7AO6hsmrEzGbg+h2A09HboUyDgu+xsmj7JnvJD39Irt+2D0+iV8g=="], + + "@emnapi/core": ["@emnapi/core@1.4.4", "", { "dependencies": { "@emnapi/wasi-threads": "1.0.3", "tslib": "^2.4.0" } }, "sha512-A9CnAbC6ARNMKcIcrQwq6HeHCjpcBZ5wSx4U01WXCqEKlrzB9F9315WDNHkrs2xbx7YjjSxbUYxuN6EQzpcY2g=="], + + "@emnapi/runtime": ["@emnapi/runtime@1.4.4", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-hHyapA4A3gPaDCNfiqyZUStTMqIkKRshqPIuDOXv1hcBnD4U3l8cP0T1HMCfGRxQ6V64TGCcoswChANyOAwbQg=="], + + "@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.0.3", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-8K5IFFsQqF9wQNJptGbS6FNKgUTsSRYnTqNCG1vPP8jFdjSv18n2mQfJpkt2Oibo9iBEzcDnDxNwKTzC7svlJw=="], + + "@emotion/hash": ["@emotion/hash@0.8.0", "", {}, "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow=="], + + "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.7.0", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw=="], + + "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.1", "", {}, "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ=="], + + "@eslint/config-array": ["@eslint/config-array@0.21.0", "", { "dependencies": { "@eslint/object-schema": "^2.1.6", "debug": "^4.3.1", "minimatch": "^3.1.2" } }, "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ=="], + + "@eslint/config-helpers": ["@eslint/config-helpers@0.3.0", "", {}, "sha512-ViuymvFmcJi04qdZeDc2whTHryouGcDlaxPqarTD0ZE10ISpxGUVZGZDx4w01upyIynL3iu6IXH2bS1NhclQMw=="], + + "@eslint/core": ["@eslint/core@0.15.1", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA=="], + + "@eslint/eslintrc": ["@eslint/eslintrc@3.3.1", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ=="], + + "@eslint/js": ["@eslint/js@9.31.0", "", {}, "sha512-LOm5OVt7D4qiKCqoiPbA7LWmI+tbw1VbTUowBcUMgQSuM6poJufkFkYDcQpo5KfgD39TnNySV26QjOh7VFpSyw=="], + + "@eslint/object-schema": ["@eslint/object-schema@2.1.6", "", {}, "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA=="], + + "@eslint/plugin-kit": ["@eslint/plugin-kit@0.3.3", "", { "dependencies": { "@eslint/core": "^0.15.1", "levn": "^0.4.1" } }, "sha512-1+WqvgNMhmlAambTvT3KPtCl/Ibr68VldY2XY40SL1CE0ZXiakFR/cbTspaF5HsnpDMvcYYoJHfl4980NBjGag=="], + + "@fontsource-variable/nunito": ["@fontsource-variable/nunito@5.2.6", "", {}, "sha512-dGYTQ0Hl94jjfMraYefrURHGH8fk/vL/1zYAZGofiPJVs6C0OkM8T87Te5Gwrbe6HG/XEMm5lib8AqasTN3ucw=="], + + "@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="], + + "@humanfs/node": ["@humanfs/node@0.16.6", "", { "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.3.0" } }, "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw=="], + + "@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="], + + "@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="], + + "@isaacs/fs-minipass": ["@isaacs/fs-minipass@4.0.1", "", { "dependencies": { "minipass": "^7.0.4" } }, "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w=="], + + "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.12", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg=="], + + "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], + + "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.4", "", {}, "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw=="], + + "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.29", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ=="], + + "@juggle/resize-observer": ["@juggle/resize-observer@3.4.0", "", {}, "sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA=="], + + "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@0.2.12", "", { "dependencies": { "@emnapi/core": "^1.4.3", "@emnapi/runtime": "^1.4.3", "@tybys/wasm-util": "^0.10.0" } }, "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ=="], + + "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="], + + "@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="], + + "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="], + + "@oxc-project/runtime": ["@oxc-project/runtime@0.77.0", "", {}, "sha512-cMbHs/DaomWSjxeJ79G10GA5hzJW9A7CZ+/cO+KuPZ7Trf3Rr07qSLauC4Ns8ba4DKVDjd8VSC9nVLpw6jpoGQ=="], + + "@oxc-project/types": ["@oxc-project/types@0.77.0", "", {}, "sha512-iUQj185VvCPnSba+ltUV5tVDrPX6LeZVtQywnnoGbe4oJ1VKvDKisjGkD/AvVtdm98b/BdsVS35IlJV1m2mBBA=="], + + "@oxlint/darwin-arm64": ["@oxlint/darwin-arm64@1.1.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-sSnR3SOxIU/QfaqXrcQ0UVUkzJO0bcInQ7dMhHa102gVAgWjp1fBeMVCM0adEY0UNmEXrRkgD/rQtQgn9YAU+w=="], + + "@oxlint/darwin-x64": ["@oxlint/darwin-x64@1.1.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-Jvd3fHnzY2OYbmsg9NSGPoBkGViDGHSFnBKyJQ9LOIw7lxAyQBG2Quxc3GYPFR/f9OYho9C3p4+dIaAJfKhnsw=="], + + "@oxlint/linux-arm64-gnu": ["@oxlint/linux-arm64-gnu@1.1.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-MgW4iskOdXuoR+wDXIJUfbdnTg2eo2FnQRaD6ZqhnDTDa7LnV+06rp/Cg3aGj2X9jSEcKDv/bMbYQuot7WRs6Q=="], + + "@oxlint/linux-arm64-musl": ["@oxlint/linux-arm64-musl@1.1.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-a+pkEKmDRdrW+y0gtZ/m68ElVW2VZgATGbMxDgDYFpdiMx9Y0pUPwTMZ2EX/17Aslop4c1BiDSFDK7aEBxKR2g=="], + + "@oxlint/linux-x64-gnu": ["@oxlint/linux-x64-gnu@1.1.0", "", { "os": "linux", "cpu": "x64" }, "sha512-wNBsXCKVZMvUTcFitrV1wTsdhUAv8l+XQxHxciZ2SO6dpNnWEb2YCxSAIOXeyzBLdO4pIODYcSy38CvGue7TwA=="], + + "@oxlint/linux-x64-musl": ["@oxlint/linux-x64-musl@1.1.0", "", { "os": "linux", "cpu": "x64" }, "sha512-pZD0lt6A5j2Wp70fgIYk4GoPfKTZ8mHWamWIpKFT7aSkFkiOi6nhLWDFvMEIHWRTK3LgkWUNcnWPp4brvin4wQ=="], + + "@oxlint/win32-arm64": ["@oxlint/win32-arm64@1.1.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-rT6uXQvE80+B+L04HJf30uF26426FPI9i9DAY2AxBUhrpNwhqkDEhQdd9ilFWVC7SSbpHgAs50lo+ImSAAkHPQ=="], + + "@oxlint/win32-x64": ["@oxlint/win32-x64@1.1.0", "", { "os": "win32", "cpu": "x64" }, "sha512-x6r5yvM3wEty93Bx0NuNK+kutUyS/K55itkUrxdExoK6GcmVDboGGuhju9HyU2cM/IWLEWO8RHcXSyaxr9GR5g=="], + + "@pkgr/core": ["@pkgr/core@0.2.7", "", {}, "sha512-YLT9Zo3oNPJoBjBc4q8G2mjU4tqIbf5CEOORbUUr48dCD9q3umJ3IPlVqOqDakPfd2HuwccBaqlGhN4Gmr5OWg=="], + + "@polka/url": ["@polka/url@1.0.0-next.29", "", {}, "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww=="], + + "@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.0.0-beta.27", "", { "os": "android", "cpu": "arm64" }, "sha512-IJL3efUJmvb5MfTEi7bGK4jq3ZFAzVbSy+vmul0DcdrglUd81Tfyy7Zzq2oM0tUgmACG32d8Jz/ykbpbf+3C5A=="], + + "@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.0.0-beta.27", "", { "os": "darwin", "cpu": "arm64" }, "sha512-TXTiuHbtnHfb0c44vNfWfIyEFJ0BFUf63ip9Z4mj8T2zRcZXQYVger4OuAxnwGNGBgDyHo1VaNBG+Vxn2VrpqQ=="], + + "@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.0.0-beta.27", "", { "os": "darwin", "cpu": "x64" }, "sha512-Jpjflgvbolh+fAaaEajPJQCOpZMawYMbNVzuZp3nidX1B7kMAP7NEKp9CWzthoL2Y8RfD7OApN6bx4+vFurTaw=="], + + "@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.0.0-beta.27", "", { "os": "freebsd", "cpu": "x64" }, "sha512-07ZNlXIunyS1jCTnene7aokkzCZNBUnmnJWu4Nz5X5XQvVHJNjsDhPFJTlNmneSDzA3vGkRNwdECKXiDTH/CqA=="], + + "@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.27", "", { "os": "linux", "cpu": "arm" }, "sha512-z74ah00oyKnTUtaIbg34TaIU1PYM8tGE1bK6aUs8OLZ9sWW4g3Xo5A0nit2zyeanmYFvrAUxnt3Bpk+mTZCtlg=="], + + "@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.0.0-beta.27", "", { "os": "linux", "cpu": "arm64" }, "sha512-b9oKl/M5OIyAcosS73BmjOZOjvcONV97t2SnKpgwfDX/mjQO3dBgTYyvHMFA6hfhIDW1+2XVQR/k5uzBULFhoA=="], + + "@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.0.0-beta.27", "", { "os": "linux", "cpu": "arm64" }, "sha512-RmaNSkVmAH8u/r5Q+v4O0zL4HY8pLrvlM5wBoBrb/QHDQgksGKBqhecpg1ERER0Q7gMh/GJUz6JiiD55Q+9UOA=="], + + "@rolldown/binding-linux-arm64-ohos": ["@rolldown/binding-linux-arm64-ohos@1.0.0-beta.27", "", { "os": "none", "cpu": "arm64" }, "sha512-gq78fI/g0cp1UKFMk53kP/oZAgYOXbaqdadVMuCJc0CoSkDJcpO2YIasRs/QYlE91QWfcHD5RZl9zbf4ksTS/w=="], + + "@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.0.0-beta.27", "", { "os": "linux", "cpu": "x64" }, "sha512-yS/GreJ6BT44dHu1WLigc50S8jZA+pDzzsf8tqRptUTwi5YW7dX3NqcDlc/lXsZqu57aKynLljgClYAm90LEKw=="], + + "@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.0.0-beta.27", "", { "os": "linux", "cpu": "x64" }, "sha512-6FV9To1sXewGHY4NaCPeOE5p5o1qfuAjj+m75WVIPw9HEJVsQoC5QiTL5wWVNqSMch4X0eWnQ6WsQolU6sGMIA=="], + + "@rolldown/binding-wasm32-wasi": ["@rolldown/binding-wasm32-wasi@1.0.0-beta.27", "", { "dependencies": { "@napi-rs/wasm-runtime": "^0.2.12" }, "cpu": "none" }, "sha512-VcxdhF0PQda9krFJHw4DqUkdAsHWYs/Uz/Kr/zhU8zMFDzmK6OdUgl9emGj9wTzXAEHYkAMDhk+OJBRJvp424g=="], + + "@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.0.0-beta.27", "", { "os": "win32", "cpu": "arm64" }, "sha512-3bXSARqSf8jLHrQ1/tw9pX1GwIR9jA6OEsqTgdC0DdpoZ+34sbJXE9Nse3dQ0foGLKBkh4PqDv/rm2Thu9oVBw=="], + + "@rolldown/binding-win32-ia32-msvc": ["@rolldown/binding-win32-ia32-msvc@1.0.0-beta.27", "", { "os": "win32", "cpu": "ia32" }, "sha512-xPGcKb+W8NIWAf5KApsUIrhiKH5NImTarICge5jQ2m0BBxD31crio4OXy/eYVq5CZkqkqszLQz2fWZcWNmbzlQ=="], + + "@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.0.0-beta.27", "", { "os": "win32", "cpu": "x64" }, "sha512-3y1G8ARpXBAcz4RJM5nzMU6isS/gXZl8SuX8lS2piFOnQMiOp6ajeelnciD+EgG4ej793zvNvr+WZtdnao2yrw=="], + + "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.19", "", {}, "sha512-3FL3mnMbPu0muGOCaKAhhFEYmqv9eTfPSJRJmANrCwtgK8VuxpsZDGK+m0LYAGoyO8+0j5uRe4PeyPDK1yA/hA=="], + + "@rollup/pluginutils": ["@rollup/pluginutils@5.2.0", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-qWJ2ZTbmumwiLFomfzTyt5Kng4hwPi9rwCYN4SHb6eaRU1KNO4ccxINHr/VhH4GgPlt1XfSTLX2LBTme8ne4Zw=="], + + "@sec-ant/readable-stream": ["@sec-ant/readable-stream@0.4.1", "", {}, "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg=="], + + "@sindresorhus/merge-streams": ["@sindresorhus/merge-streams@4.0.0", "", {}, "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ=="], + + "@tailwindcss/node": ["@tailwindcss/node@4.1.11", "", { "dependencies": { "@ampproject/remapping": "^2.3.0", "enhanced-resolve": "^5.18.1", "jiti": "^2.4.2", "lightningcss": "1.30.1", "magic-string": "^0.30.17", "source-map-js": "^1.2.1", "tailwindcss": "4.1.11" } }, "sha512-yzhzuGRmv5QyU9qLNg4GTlYI6STedBWRE7NjxP45CsFYYq9taI0zJXZBMqIC/c8fViNLhmrbpSFS57EoxUmD6Q=="], + + "@tailwindcss/oxide": ["@tailwindcss/oxide@4.1.11", "", { "dependencies": { "detect-libc": "^2.0.4", "tar": "^7.4.3" }, "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.1.11", "@tailwindcss/oxide-darwin-arm64": "4.1.11", "@tailwindcss/oxide-darwin-x64": "4.1.11", "@tailwindcss/oxide-freebsd-x64": "4.1.11", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.11", "@tailwindcss/oxide-linux-arm64-gnu": "4.1.11", "@tailwindcss/oxide-linux-arm64-musl": "4.1.11", "@tailwindcss/oxide-linux-x64-gnu": "4.1.11", "@tailwindcss/oxide-linux-x64-musl": "4.1.11", "@tailwindcss/oxide-wasm32-wasi": "4.1.11", "@tailwindcss/oxide-win32-arm64-msvc": "4.1.11", "@tailwindcss/oxide-win32-x64-msvc": "4.1.11" } }, "sha512-Q69XzrtAhuyfHo+5/HMgr1lAiPP/G40OMFAnws7xcFEYqcypZmdW8eGXaOUIeOl1dzPJBPENXgbjsOyhg2nkrg=="], + + "@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.1.11", "", { "os": "android", "cpu": "arm64" }, "sha512-3IfFuATVRUMZZprEIx9OGDjG3Ou3jG4xQzNTvjDoKmU9JdmoCohQJ83MYd0GPnQIu89YoJqvMM0G3uqLRFtetg=="], + + "@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.1.11", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ESgStEOEsyg8J5YcMb1xl8WFOXfeBmrhAwGsFxxB2CxY9evy63+AtpbDLAyRkJnxLy2WsD1qF13E97uQyP1lfQ=="], + + "@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.1.11", "", { "os": "darwin", "cpu": "x64" }, "sha512-EgnK8kRchgmgzG6jE10UQNaH9Mwi2n+yw1jWmof9Vyg2lpKNX2ioe7CJdf9M5f8V9uaQxInenZkOxnTVL3fhAw=="], + + "@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.1.11", "", { "os": "freebsd", "cpu": "x64" }, "sha512-xdqKtbpHs7pQhIKmqVpxStnY1skuNh4CtbcyOHeX1YBE0hArj2romsFGb6yUmzkq/6M24nkxDqU8GYrKrz+UcA=="], + + "@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.1.11", "", { "os": "linux", "cpu": "arm" }, "sha512-ryHQK2eyDYYMwB5wZL46uoxz2zzDZsFBwfjssgB7pzytAeCCa6glsiJGjhTEddq/4OsIjsLNMAiMlHNYnkEEeg=="], + + "@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.1.11", "", { "os": "linux", "cpu": "arm64" }, "sha512-mYwqheq4BXF83j/w75ewkPJmPZIqqP1nhoghS9D57CLjsh3Nfq0m4ftTotRYtGnZd3eCztgbSPJ9QhfC91gDZQ=="], + + "@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.1.11", "", { "os": "linux", "cpu": "arm64" }, "sha512-m/NVRFNGlEHJrNVk3O6I9ggVuNjXHIPoD6bqay/pubtYC9QIdAMpS+cswZQPBLvVvEF6GtSNONbDkZrjWZXYNQ=="], + + "@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.1.11", "", { "os": "linux", "cpu": "x64" }, "sha512-YW6sblI7xukSD2TdbbaeQVDysIm/UPJtObHJHKxDEcW2exAtY47j52f8jZXkqE1krdnkhCMGqP3dbniu1Te2Fg=="], + + "@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.1.11", "", { "os": "linux", "cpu": "x64" }, "sha512-e3C/RRhGunWYNC3aSF7exsQkdXzQ/M+aYuZHKnw4U7KQwTJotnWsGOIVih0s2qQzmEzOFIJ3+xt7iq67K/p56Q=="], + + "@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.1.11", "", { "dependencies": { "@emnapi/core": "^1.4.3", "@emnapi/runtime": "^1.4.3", "@emnapi/wasi-threads": "^1.0.2", "@napi-rs/wasm-runtime": "^0.2.11", "@tybys/wasm-util": "^0.9.0", "tslib": "^2.8.0" }, "cpu": "none" }, "sha512-Xo1+/GU0JEN/C/dvcammKHzeM6NqKovG+6921MR6oadee5XPBaKOumrJCXvopJ/Qb5TH7LX/UAywbqrP4lax0g=="], + + "@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.1.11", "", { "os": "win32", "cpu": "arm64" }, "sha512-UgKYx5PwEKrac3GPNPf6HVMNhUIGuUh4wlDFR2jYYdkX6pL/rn73zTq/4pzUm8fOjAn5L8zDeHp9iXmUGOXZ+w=="], + + "@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.1.11", "", { "os": "win32", "cpu": "x64" }, "sha512-YfHoggn1j0LK7wR82TOucWc5LDCguHnoS879idHekmmiR7g9HUtMw9MI0NHatS28u/Xlkfi9w5RJWgz2Dl+5Qg=="], + + "@tailwindcss/vite": ["@tailwindcss/vite@4.1.11", "", { "dependencies": { "@tailwindcss/node": "4.1.11", "@tailwindcss/oxide": "4.1.11", "tailwindcss": "4.1.11" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7" } }, "sha512-RHYhrR3hku0MJFRV+fN2gNbDNEh3dwKvY8XJvTxCSXeMOsCRSr+uKvDWQcbizrHgjML6ZmTE5OwMrl5wKcujCw=="], + + "@tsconfig/node22": ["@tsconfig/node22@22.0.2", "", {}, "sha512-Kmwj4u8sDRDrMYRoN9FDEcXD8UpBSaPQQ24Gz+Gamqfm7xxn+GBR7ge/Z7pK8OXNGyUzbSwJj+TH6B+DS/epyA=="], + + "@tybys/wasm-util": ["@tybys/wasm-util@0.10.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-VyyPYFlOMNylG45GoAe0xDoLwWuowvf92F9kySqzYh8vmYm7D2u4iUJKa1tOUpS70Ku13ASrOkS4ScXFsTaCNQ=="], + + "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], + + "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], + + "@types/katex": ["@types/katex@0.16.7", "", {}, "sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ=="], + + "@types/lodash": ["@types/lodash@4.17.20", "", {}, "sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA=="], + + "@types/lodash-es": ["@types/lodash-es@4.17.12", "", { "dependencies": { "@types/lodash": "*" } }, "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ=="], + + "@types/node": ["@types/node@22.16.4", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-PYRhNtZdm2wH/NT2k/oAJ6/f2VD2N2Dag0lGlx2vWgMSJXGNmlce5MiTQzoWAiIJtso30mjnfQCOKVH+kAQC/g=="], + + "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.37.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.37.0", "@typescript-eslint/type-utils": "8.37.0", "@typescript-eslint/utils": "8.37.0", "@typescript-eslint/visitor-keys": "8.37.0", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.37.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-jsuVWeIkb6ggzB+wPCsR4e6loj+rM72ohW6IBn2C+5NCvfUVY8s33iFPySSVXqtm5Hu29Ne/9bnA0JmyLmgenA=="], + + "@typescript-eslint/parser": ["@typescript-eslint/parser@8.37.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.37.0", "@typescript-eslint/types": "8.37.0", "@typescript-eslint/typescript-estree": "8.37.0", "@typescript-eslint/visitor-keys": "8.37.0", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-kVIaQE9vrN9RLCQMQ3iyRlVJpTiDUY6woHGb30JDkfJErqrQEmtdWH3gV0PBAfGZgQXoqzXOO0T3K6ioApbbAA=="], + + "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.37.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.37.0", "@typescript-eslint/types": "^8.37.0", "debug": "^4.3.4" }, "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-BIUXYsbkl5A1aJDdYJCBAo8rCEbAvdquQ8AnLb6z5Lp1u3x5PNgSSx9A/zqYc++Xnr/0DVpls8iQ2cJs/izTXA=="], + + "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.37.0", "", { "dependencies": { "@typescript-eslint/types": "8.37.0", "@typescript-eslint/visitor-keys": "8.37.0" } }, "sha512-0vGq0yiU1gbjKob2q691ybTg9JX6ShiVXAAfm2jGf3q0hdP6/BruaFjL/ManAR/lj05AvYCH+5bbVo0VtzmjOA=="], + + "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.37.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-1/YHvAVTimMM9mmlPvTec9NP4bobA1RkDbMydxG8omqwJJLEW/Iy2C4adsAESIXU3WGLXFHSZUU+C9EoFWl4Zg=="], + + "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.37.0", "", { "dependencies": { "@typescript-eslint/types": "8.37.0", "@typescript-eslint/typescript-estree": "8.37.0", "@typescript-eslint/utils": "8.37.0", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-SPkXWIkVZxhgwSwVq9rqj/4VFo7MnWwVaRNznfQDc/xPYHjXnPfLWn+4L6FF1cAz6e7dsqBeMawgl7QjUMj4Ow=="], + + "@typescript-eslint/types": ["@typescript-eslint/types@8.37.0", "", {}, "sha512-ax0nv7PUF9NOVPs+lmQ7yIE7IQmAf8LGcXbMvHX5Gm+YJUYNAl340XkGnrimxZ0elXyoQJuN5sbg6C4evKA4SQ=="], + + "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.37.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.37.0", "@typescript-eslint/tsconfig-utils": "8.37.0", "@typescript-eslint/types": "8.37.0", "@typescript-eslint/visitor-keys": "8.37.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-zuWDMDuzMRbQOM+bHyU4/slw27bAUEcKSKKs3hcv2aNnc/tvE/h7w60dwVw8vnal2Pub6RT1T7BI8tFZ1fE+yg=="], + + "@typescript-eslint/utils": ["@typescript-eslint/utils@8.37.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.37.0", "@typescript-eslint/types": "8.37.0", "@typescript-eslint/typescript-estree": "8.37.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-TSFvkIW6gGjN2p6zbXo20FzCABbyUAuq6tBvNRGsKdsSQ6a7rnV6ADfZ7f4iI3lIiXc4F4WWvtUfDw9CJ9pO5A=="], + + "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.37.0", "", { "dependencies": { "@typescript-eslint/types": "8.37.0", "eslint-visitor-keys": "^4.2.1" } }, "sha512-YzfhzcTnZVPiLfP/oeKtDp2evwvHLMe0LOy7oe+hb9KKIumLNohYS9Hgp1ifwpu42YWxhZE8yieggz6JpqO/1w=="], + + "@vicons/material": ["@vicons/material@0.13.0", "", {}, "sha512-lKVxFNprM+CaBkUH3gt6VjIeiMsKQl2zARQMwTCZruQl2vRHzyeZiKeCflWS99CEfv2JzX/6y697smxlzyxcVw=="], + + "@vitejs/plugin-vue": ["@vitejs/plugin-vue@6.0.0", "", { "dependencies": { "@rolldown/pluginutils": "1.0.0-beta.19" }, "peerDependencies": { "vite": "^5.0.0 || ^6.0.0 || ^7.0.0", "vue": "^3.2.25" } }, "sha512-iAliE72WsdhjzTOp2DtvKThq1VBC4REhwRcaA+zPAAph6I+OQhUXv+Xu2KS7ElxYtb7Zc/3R30Hwv1DxEo7NXQ=="], + + "@vitejs/plugin-vue-jsx": ["@vitejs/plugin-vue-jsx@5.0.1", "", { "dependencies": { "@babel/core": "^7.27.7", "@babel/plugin-transform-typescript": "^7.27.1", "@rolldown/pluginutils": "^1.0.0-beta.21", "@vue/babel-plugin-jsx": "^1.4.0" }, "peerDependencies": { "vite": "^5.0.0 || ^6.0.0 || ^7.0.0", "vue": "^3.0.0" } }, "sha512-X7qmQMXbdDh+sfHUttXokPD0cjPkMFoae7SgbkF9vi3idGUKmxLcnU2Ug49FHwiKXebfzQRIm5yK3sfCJzNBbg=="], + + "@volar/language-core": ["@volar/language-core@2.4.15", "", { "dependencies": { "@volar/source-map": "2.4.15" } }, "sha512-3VHw+QZU0ZG9IuQmzT68IyN4hZNd9GchGPhbD9+pa8CVv7rnoOZwo7T8weIbrRmihqy3ATpdfXFnqRrfPVK6CA=="], + + "@volar/source-map": ["@volar/source-map@2.4.15", "", {}, "sha512-CPbMWlUN6hVZJYGcU/GSoHu4EnCHiLaXI9n8c9la6RaI9W5JHX+NqG+GSQcB0JdC2FIBLdZJwGsfKyBB71VlTg=="], + + "@volar/typescript": ["@volar/typescript@2.4.15", "", { "dependencies": { "@volar/language-core": "2.4.15", "path-browserify": "^1.0.1", "vscode-uri": "^3.0.8" } }, "sha512-2aZ8i0cqPGjXb4BhkMsPYDkkuc2ZQ6yOpqwAuNwUoncELqoy5fRgOQtLR9gB0g902iS0NAkvpIzs27geVyVdPg=="], + + "@vue/babel-helper-vue-transform-on": ["@vue/babel-helper-vue-transform-on@1.4.0", "", {}, "sha512-mCokbouEQ/ocRce/FpKCRItGo+013tHg7tixg3DUNS+6bmIchPt66012kBMm476vyEIJPafrvOf4E5OYj3shSw=="], + + "@vue/babel-plugin-jsx": ["@vue/babel-plugin-jsx@1.4.0", "", { "dependencies": { "@babel/helper-module-imports": "^7.25.9", "@babel/helper-plugin-utils": "^7.26.5", "@babel/plugin-syntax-jsx": "^7.25.9", "@babel/template": "^7.26.9", "@babel/traverse": "^7.26.9", "@babel/types": "^7.26.9", "@vue/babel-helper-vue-transform-on": "1.4.0", "@vue/babel-plugin-resolve-type": "1.4.0", "@vue/shared": "^3.5.13" }, "peerDependencies": { "@babel/core": "^7.0.0-0" }, "optionalPeers": ["@babel/core"] }, "sha512-9zAHmwgMWlaN6qRKdrg1uKsBKHvnUU+Py+MOCTuYZBoZsopa90Di10QRjB+YPnVss0BZbG/H5XFwJY1fTxJWhA=="], + + "@vue/babel-plugin-resolve-type": ["@vue/babel-plugin-resolve-type@1.4.0", "", { "dependencies": { "@babel/code-frame": "^7.26.2", "@babel/helper-module-imports": "^7.25.9", "@babel/helper-plugin-utils": "^7.26.5", "@babel/parser": "^7.26.9", "@vue/compiler-sfc": "^3.5.13" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-4xqDRRbQQEWHQyjlYSgZsWj44KfiF6D+ktCuXyZ8EnVDYV3pztmXJDf1HveAjUAXxAnR8daCQT51RneWWxtTyQ=="], + + "@vue/compiler-core": ["@vue/compiler-core@3.5.17", "", { "dependencies": { "@babel/parser": "^7.27.5", "@vue/shared": "3.5.17", "entities": "^4.5.0", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "sha512-Xe+AittLbAyV0pabcN7cP7/BenRBNcteM4aSDCtRvGw0d9OL+HG1u/XHLY/kt1q4fyMeZYXyIYrsHuPSiDPosA=="], + + "@vue/compiler-dom": ["@vue/compiler-dom@3.5.17", "", { "dependencies": { "@vue/compiler-core": "3.5.17", "@vue/shared": "3.5.17" } }, "sha512-+2UgfLKoaNLhgfhV5Ihnk6wB4ljyW1/7wUIog2puUqajiC29Lp5R/IKDdkebh9jTbTogTbsgB+OY9cEWzG95JQ=="], + + "@vue/compiler-sfc": ["@vue/compiler-sfc@3.5.17", "", { "dependencies": { "@babel/parser": "^7.27.5", "@vue/compiler-core": "3.5.17", "@vue/compiler-dom": "3.5.17", "@vue/compiler-ssr": "3.5.17", "@vue/shared": "3.5.17", "estree-walker": "^2.0.2", "magic-string": "^0.30.17", "postcss": "^8.5.6", "source-map-js": "^1.2.1" } }, "sha512-rQQxbRJMgTqwRugtjw0cnyQv9cP4/4BxWfTdRBkqsTfLOHWykLzbOc3C4GGzAmdMDxhzU/1Ija5bTjMVrddqww=="], + + "@vue/compiler-ssr": ["@vue/compiler-ssr@3.5.17", "", { "dependencies": { "@vue/compiler-dom": "3.5.17", "@vue/shared": "3.5.17" } }, "sha512-hkDbA0Q20ZzGgpj5uZjb9rBzQtIHLS78mMilwrlpWk2Ep37DYntUz0PonQ6kr113vfOEdM+zTBuJDaceNIW0tQ=="], + + "@vue/compiler-vue2": ["@vue/compiler-vue2@2.7.16", "", { "dependencies": { "de-indent": "^1.0.2", "he": "^1.2.0" } }, "sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A=="], + + "@vue/devtools-api": ["@vue/devtools-api@7.7.7", "", { "dependencies": { "@vue/devtools-kit": "^7.7.7" } }, "sha512-lwOnNBH2e7x1fIIbVT7yF5D+YWhqELm55/4ZKf45R9T8r9dE2AIOy8HKjfqzGsoTHFbWbr337O4E0A0QADnjBg=="], + + "@vue/devtools-core": ["@vue/devtools-core@7.7.7", "", { "dependencies": { "@vue/devtools-kit": "^7.7.7", "@vue/devtools-shared": "^7.7.7", "mitt": "^3.0.1", "nanoid": "^5.1.0", "pathe": "^2.0.3", "vite-hot-client": "^2.0.4" }, "peerDependencies": { "vue": "^3.0.0" } }, "sha512-9z9TLbfC+AjAi1PQyWX+OErjIaJmdFlbDHcD+cAMYKY6Bh5VlsAtCeGyRMrXwIlMEQPukvnWt3gZBLwTAIMKzQ=="], + + "@vue/devtools-kit": ["@vue/devtools-kit@7.7.7", "", { "dependencies": { "@vue/devtools-shared": "^7.7.7", "birpc": "^2.3.0", "hookable": "^5.5.3", "mitt": "^3.0.1", "perfect-debounce": "^1.0.0", "speakingurl": "^14.0.1", "superjson": "^2.2.2" } }, "sha512-wgoZtxcTta65cnZ1Q6MbAfePVFxfM+gq0saaeytoph7nEa7yMXoi6sCPy4ufO111B9msnw0VOWjPEFCXuAKRHA=="], + + "@vue/devtools-shared": ["@vue/devtools-shared@7.7.7", "", { "dependencies": { "rfdc": "^1.4.1" } }, "sha512-+udSj47aRl5aKb0memBvcUG9koarqnxNM5yjuREvqwK6T3ap4mn3Zqqc17QrBFTqSMjr3HK1cvStEZpMDpfdyw=="], + + "@vue/eslint-config-prettier": ["@vue/eslint-config-prettier@10.2.0", "", { "dependencies": { "eslint-config-prettier": "^10.0.1", "eslint-plugin-prettier": "^5.2.2" }, "peerDependencies": { "eslint": ">= 8.21.0", "prettier": ">= 3.0.0" } }, "sha512-GL3YBLwv/+b86yHcNNfPJxOTtVFJ4Mbc9UU3zR+KVoG7SwGTjPT+32fXamscNumElhcpXW3mT0DgzS9w32S7Bw=="], + + "@vue/eslint-config-typescript": ["@vue/eslint-config-typescript@14.6.0", "", { "dependencies": { "@typescript-eslint/utils": "^8.35.1", "fast-glob": "^3.3.3", "typescript-eslint": "^8.35.1", "vue-eslint-parser": "^10.2.0" }, "peerDependencies": { "eslint": "^9.10.0", "eslint-plugin-vue": "^9.28.0 || ^10.0.0", "typescript": ">=4.8.4" }, "optionalPeers": ["typescript"] }, "sha512-UpiRY/7go4Yps4mYCjkvlIbVWmn9YvPGQDxTAlcKLphyaD77LjIu3plH4Y9zNT0GB4f3K5tMmhhtRhPOgrQ/bQ=="], + + "@vue/language-core": ["@vue/language-core@2.2.12", "", { "dependencies": { "@volar/language-core": "2.4.15", "@vue/compiler-dom": "^3.5.0", "@vue/compiler-vue2": "^2.7.16", "@vue/shared": "^3.5.0", "alien-signals": "^1.0.3", "minimatch": "^9.0.3", "muggle-string": "^0.4.1", "path-browserify": "^1.0.1" }, "peerDependencies": { "typescript": "*" }, "optionalPeers": ["typescript"] }, "sha512-IsGljWbKGU1MZpBPN+BvPAdr55YPkj2nB/TBNGNC32Vy2qLG25DYu/NBN2vNtZqdRbTRjaoYrahLrToim2NanA=="], + + "@vue/reactivity": ["@vue/reactivity@3.5.17", "", { "dependencies": { "@vue/shared": "3.5.17" } }, "sha512-l/rmw2STIscWi7SNJp708FK4Kofs97zc/5aEPQh4bOsReD/8ICuBcEmS7KGwDj5ODQLYWVN2lNibKJL1z5b+Lw=="], + + "@vue/runtime-core": ["@vue/runtime-core@3.5.17", "", { "dependencies": { "@vue/reactivity": "3.5.17", "@vue/shared": "3.5.17" } }, "sha512-QQLXa20dHg1R0ri4bjKeGFKEkJA7MMBxrKo2G+gJikmumRS7PTD4BOU9FKrDQWMKowz7frJJGqBffYMgQYS96Q=="], + + "@vue/runtime-dom": ["@vue/runtime-dom@3.5.17", "", { "dependencies": { "@vue/reactivity": "3.5.17", "@vue/runtime-core": "3.5.17", "@vue/shared": "3.5.17", "csstype": "^3.1.3" } }, "sha512-8El0M60TcwZ1QMz4/os2MdlQECgGoVHPuLnQBU3m9h3gdNRW9xRmI8iLS4t/22OQlOE6aJvNNlBiCzPHur4H9g=="], + + "@vue/server-renderer": ["@vue/server-renderer@3.5.17", "", { "dependencies": { "@vue/compiler-ssr": "3.5.17", "@vue/shared": "3.5.17" }, "peerDependencies": { "vue": "3.5.17" } }, "sha512-BOHhm8HalujY6lmC3DbqF6uXN/K00uWiEeF22LfEsm9Q93XeJ/plHTepGwf6tqFcF7GA5oGSSAAUock3VvzaCA=="], + + "@vue/shared": ["@vue/shared@3.5.17", "", {}, "sha512-CabR+UN630VnsJO/jHWYBC1YVXyMq94KKp6iF5MQgZJs5I8cmjw6oVMO1oDbtBkENSHSSn/UadWlW/OAgdmKrg=="], + + "@vue/tsconfig": ["@vue/tsconfig@0.7.0", "", { "peerDependencies": { "typescript": "5.x", "vue": "^3.4.0" }, "optionalPeers": ["typescript", "vue"] }, "sha512-ku2uNz5MaZ9IerPPUyOHzyjhXoX2kVJaVf7hL315DC17vS6IiZRmmCPfggNbU16QTvM80+uYYy3eYJB59WCtvg=="], + + "acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="], + + "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], + + "ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="], + + "alien-signals": ["alien-signals@1.0.13", "", {}, "sha512-OGj9yyTnJEttvzhTUWuscOvtqxq5vrhF7vL9oS0xJ2mK0ItPYP1/y+vCFebfxoEyAz0++1AIwJ5CMr+Fk3nDmg=="], + + "ansi-styles": ["ansi-styles@6.2.1", "", {}, "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug=="], + + "ansis": ["ansis@4.1.0", "", {}, "sha512-BGcItUBWSMRgOCe+SVZJ+S7yTRG0eGt9cXAHev72yuGcY23hnLA7Bky5L/xLyPINoSN95geovfBkqoTlNZYa7w=="], + + "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], + + "aspnet-prerendering": ["aspnet-prerendering@3.0.1", "", { "dependencies": { "domain-task": "^3.0.0" } }, "sha512-nfOQYVKW3sYQMZBXNM2KPrXU2MOBuLn/gszRZM0Y1Pj4EpzCw1KjXiO681eQo4ZR1TLLzJ8L2sQbq0qeC1zxVg=="], + + "async-validator": ["async-validator@4.2.5", "", {}, "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg=="], + + "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + + "birpc": ["birpc@2.5.0", "", {}, "sha512-VSWO/W6nNQdyP520F1mhf+Lc2f8pjGQOtoHHm7Ze8Go1kX7akpVIrtTa0fn+HB0QJEDVacl6aO08YE0PgXfdnQ=="], + + "boolbase": ["boolbase@1.0.0", "", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="], + + "brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], + + "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], + + "browserslist": ["browserslist@4.25.1", "", { "dependencies": { "caniuse-lite": "^1.0.30001726", "electron-to-chromium": "^1.5.173", "node-releases": "^2.0.19", "update-browserslist-db": "^1.1.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw=="], + + "bundle-name": ["bundle-name@4.1.0", "", { "dependencies": { "run-applescript": "^7.0.0" } }, "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q=="], + + "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], + + "caniuse-lite": ["caniuse-lite@1.0.30001727", "", {}, "sha512-pB68nIHmbN6L/4C6MH1DokyR3bYqFwjaSs/sWDHGj4CTcFtQUQMuJftVwWkXq7mNWOybD3KhUv3oWHoGxgP14Q=="], + + "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], + + "chownr": ["chownr@3.0.0", "", {}, "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="], + + "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], + + "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], + + "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], + + "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], + + "copy-anything": ["copy-anything@3.0.5", "", { "dependencies": { "is-what": "^4.1.8" } }, "sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w=="], + + "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], + + "css-render": ["css-render@0.15.14", "", { "dependencies": { "@emotion/hash": "~0.8.0", "csstype": "~3.0.5" } }, "sha512-9nF4PdUle+5ta4W5SyZdLCCmFd37uVimSjg1evcTqKJCyvCEEj12WKzOSBNak6r4im4J4iYXKH1OWpUV5LBYFg=="], + + "cssesc": ["cssesc@3.0.0", "", { "bin": { "cssesc": "bin/cssesc" } }, "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="], + + "csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="], + + "date-fns": ["date-fns@3.6.0", "", {}, "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww=="], + + "date-fns-tz": ["date-fns-tz@3.2.0", "", { "peerDependencies": { "date-fns": "^3.0.0 || ^4.0.0" } }, "sha512-sg8HqoTEulcbbbVXeg84u5UnlsQa8GS5QXMqjjYIhS4abEVVKIUwe0/l/UhrZdKaL/W5eWZNlbTeEIiOXTcsBQ=="], + + "de-indent": ["de-indent@1.0.2", "", {}, "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg=="], + + "debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="], + + "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="], + + "default-browser": ["default-browser@5.2.1", "", { "dependencies": { "bundle-name": "^4.1.0", "default-browser-id": "^5.0.0" } }, "sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg=="], + + "default-browser-id": ["default-browser-id@5.0.0", "", {}, "sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA=="], + + "define-lazy-prop": ["define-lazy-prop@3.0.0", "", {}, "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg=="], + + "detect-libc": ["detect-libc@2.0.4", "", {}, "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA=="], + + "domain-context": ["domain-context@0.5.1", "", {}, "sha512-WyTWkXciNvYYaQzdnKJtjlVSXHivtt0E/vCv36Bkwh+Sk4NXkrQpHxZT5BHYmKRVgxWMol1wcdurZCzyTT6Euw=="], + + "domain-task": ["domain-task@3.0.3", "", { "dependencies": { "domain-context": "^0.5.1", "is-absolute-url": "^2.1.0", "isomorphic-fetch": "^2.2.1" } }, "sha512-7oAiY1AvjhVNVJbOwSHbrm6lEHczOSSCSqDkHp2ZO7vb/iOCGl7YNk/1cv4yKwSGhBMpBZ5mu+7cMorbWxWvOg=="], + + "electron-to-chromium": ["electron-to-chromium@1.5.183", "", {}, "sha512-vCrDBYjQCAEefWGjlK3EpoSKfKbT10pR4XXPdn65q7snuNOZnthoVpBfZPykmDapOKfoD+MMIPG8ZjKyyc9oHA=="], + + "encoding": ["encoding@0.1.13", "", { "dependencies": { "iconv-lite": "^0.6.2" } }, "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A=="], + + "enhanced-resolve": ["enhanced-resolve@5.18.2", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-6Jw4sE1maoRJo3q8MsSIn2onJFbLTOjY9hlx4DZXmOKvLRd1Ok2kXmAGXaafL2+ijsJZ1ClYbl/pmqr9+k4iUQ=="], + + "entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], + + "error-stack-parser-es": ["error-stack-parser-es@0.1.5", "", {}, "sha512-xHku1X40RO+fO8yJ8Wh2f2rZWVjqyhb1zgq1yZ8aZRQkv6OOKhKWRUaht3eSCUbAOBaKIgM+ykwFLE+QUxgGeg=="], + + "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], + + "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], + + "eslint": ["eslint@9.31.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.0", "@eslint/config-helpers": "^0.3.0", "@eslint/core": "^0.15.0", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.31.0", "@eslint/plugin-kit": "^0.3.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-QldCVh/ztyKJJZLr4jXNUByx3gR+TDYZCRXEktiZoUR3PGy4qCmSbkxcIle8GEwGpb5JBZazlaJ/CxLidXdEbQ=="], + + "eslint-config-prettier": ["eslint-config-prettier@10.1.5", "", { "peerDependencies": { "eslint": ">=7.0.0" }, "bin": { "eslint-config-prettier": "bin/cli.js" } }, "sha512-zc1UmCpNltmVY34vuLRV61r1K27sWuX39E+uyUnY8xS2Bex88VV9cugG+UZbRSRGtGyFboj+D8JODyme1plMpw=="], + + "eslint-plugin-oxlint": ["eslint-plugin-oxlint@1.1.0", "", { "dependencies": { "jsonc-parser": "^3.3.1" } }, "sha512-spDWxcsAfoUDjSwxPrP2gfuOJ2Hrv8faqQ5Vkm90lURp4no5aWJQ09xRKmZroIPTuQCKYgG9nvnakdIbXGlijg=="], + + "eslint-plugin-prettier": ["eslint-plugin-prettier@5.5.1", "", { "dependencies": { "prettier-linter-helpers": "^1.0.0", "synckit": "^0.11.7" }, "peerDependencies": { "@types/eslint": ">=8.0.0", "eslint": ">=8.0.0", "eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0", "prettier": ">=3.0.0" }, "optionalPeers": ["@types/eslint", "eslint-config-prettier"] }, "sha512-dobTkHT6XaEVOo8IO90Q4DOSxnm3Y151QxPJlM/vKC0bVy+d6cVWQZLlFiuZPP0wS6vZwSKeJgKkcS+KfMBlRw=="], + + "eslint-plugin-vue": ["eslint-plugin-vue@10.2.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "natural-compare": "^1.4.0", "nth-check": "^2.1.1", "postcss-selector-parser": "^6.0.15", "semver": "^7.6.3", "xml-name-validator": "^4.0.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "vue-eslint-parser": "^10.0.0" } }, "sha512-tl9s+KN3z0hN2b8fV2xSs5ytGl7Esk1oSCxULLwFcdaElhZ8btYYZFrWxvh4En+czrSDtuLCeCOGa8HhEZuBdQ=="], + + "eslint-scope": ["eslint-scope@8.4.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg=="], + + "eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="], + + "espree": ["espree@10.4.0", "", { "dependencies": { "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.2.1" } }, "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ=="], + + "esquery": ["esquery@1.6.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg=="], + + "esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="], + + "estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="], + + "estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], + + "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], + + "evtd": ["evtd@0.2.4", "", {}, "sha512-qaeGN5bx63s/AXgQo8gj6fBkxge+OoLddLniox5qtLAEY5HSnuSlISXVPxnSae1dWblvTh4/HoMIB+mbMsvZzw=="], + + "execa": ["execa@9.6.0", "", { "dependencies": { "@sindresorhus/merge-streams": "^4.0.0", "cross-spawn": "^7.0.6", "figures": "^6.1.0", "get-stream": "^9.0.0", "human-signals": "^8.0.1", "is-plain-obj": "^4.1.0", "is-stream": "^4.0.1", "npm-run-path": "^6.0.0", "pretty-ms": "^9.2.0", "signal-exit": "^4.1.0", "strip-final-newline": "^4.0.0", "yoctocolors": "^2.1.1" } }, "sha512-jpWzZ1ZhwUmeWRhS7Qv3mhpOhLfwI+uAX4e5fOcXqwMR7EcJ0pj2kV1CVzHVMX/LphnKWD3LObjZCoJ71lKpHw=="], + + "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], + + "fast-diff": ["fast-diff@1.3.0", "", {}, "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw=="], + + "fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="], + + "fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="], + + "fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="], + + "fastq": ["fastq@1.19.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ=="], + + "fdir": ["fdir@6.4.6", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w=="], + + "figures": ["figures@6.1.0", "", { "dependencies": { "is-unicode-supported": "^2.0.0" } }, "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg=="], + + "file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="], + + "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], + + "find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="], + + "flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="], + + "flatted": ["flatted@3.3.3", "", {}, "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="], + + "fs-extra": ["fs-extra@11.3.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew=="], + + "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + + "gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="], + + "get-stream": ["get-stream@9.0.1", "", { "dependencies": { "@sec-ant/readable-stream": "^0.4.1", "is-stream": "^4.0.1" } }, "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA=="], + + "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], + + "globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="], + + "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], + + "graphemer": ["graphemer@1.4.0", "", {}, "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag=="], + + "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], + + "he": ["he@1.2.0", "", { "bin": { "he": "bin/he" } }, "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw=="], + + "highlight.js": ["highlight.js@11.11.1", "", {}, "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w=="], + + "hookable": ["hookable@5.5.3", "", {}, "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ=="], + + "human-signals": ["human-signals@8.0.1", "", {}, "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ=="], + + "iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], + + "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], + + "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], + + "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], + + "is-absolute-url": ["is-absolute-url@2.1.0", "", {}, "sha512-vOx7VprsKyllwjSkLV79NIhpyLfr3jAp7VaTCMXOJHu4m0Ew1CZ2fcjASwmV1jI3BWuWHB013M48eyeldk9gYg=="], + + "is-docker": ["is-docker@3.0.0", "", { "bin": { "is-docker": "cli.js" } }, "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ=="], + + "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], + + "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], + + "is-inside-container": ["is-inside-container@1.0.0", "", { "dependencies": { "is-docker": "^3.0.0" }, "bin": { "is-inside-container": "cli.js" } }, "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA=="], + + "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="], + + "is-plain-obj": ["is-plain-obj@4.1.0", "", {}, "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="], + + "is-stream": ["is-stream@4.0.1", "", {}, "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A=="], + + "is-unicode-supported": ["is-unicode-supported@2.1.0", "", {}, "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ=="], + + "is-what": ["is-what@4.1.16", "", {}, "sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A=="], + + "is-wsl": ["is-wsl@3.1.0", "", { "dependencies": { "is-inside-container": "^1.0.0" } }, "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw=="], + + "isexe": ["isexe@3.1.1", "", {}, "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ=="], + + "isomorphic-fetch": ["isomorphic-fetch@2.2.1", "", { "dependencies": { "node-fetch": "^1.0.1", "whatwg-fetch": ">=0.10.0" } }, "sha512-9c4TNAKYXM5PRyVcwUZrF3W09nQ+sO7+jydgs4ZGW9dhsLG2VOlISJABombdQqQRXCwuYG3sYV/puGf5rp0qmA=="], + + "jiti": ["jiti@2.4.2", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A=="], + + "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], + + "js-yaml": ["js-yaml@4.1.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA=="], + + "jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="], + + "json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="], + + "json-parse-even-better-errors": ["json-parse-even-better-errors@4.0.0", "", {}, "sha512-lR4MXjGNgkJc7tkQ97kb2nuEMnNCyU//XYVH0MKTGcXEiSudQ5MKGKen3C5QubYy0vmq+JGitUg92uuywGEwIA=="], + + "json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], + + "json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="], + + "json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="], + + "jsonc-parser": ["jsonc-parser@3.3.1", "", {}, "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ=="], + + "jsonfile": ["jsonfile@6.1.0", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ=="], + + "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], + + "kolorist": ["kolorist@1.8.0", "", {}, "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ=="], + + "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="], + + "lightningcss": ["lightningcss@1.30.1", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-darwin-arm64": "1.30.1", "lightningcss-darwin-x64": "1.30.1", "lightningcss-freebsd-x64": "1.30.1", "lightningcss-linux-arm-gnueabihf": "1.30.1", "lightningcss-linux-arm64-gnu": "1.30.1", "lightningcss-linux-arm64-musl": "1.30.1", "lightningcss-linux-x64-gnu": "1.30.1", "lightningcss-linux-x64-musl": "1.30.1", "lightningcss-win32-arm64-msvc": "1.30.1", "lightningcss-win32-x64-msvc": "1.30.1" } }, "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg=="], + + "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.30.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ=="], + + "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.30.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA=="], + + "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.30.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig=="], + + "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.30.1", "", { "os": "linux", "cpu": "arm" }, "sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q=="], + + "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.30.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw=="], + + "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.30.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ=="], + + "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.30.1", "", { "os": "linux", "cpu": "x64" }, "sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw=="], + + "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.30.1", "", { "os": "linux", "cpu": "x64" }, "sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ=="], + + "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.30.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA=="], + + "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.30.1", "", { "os": "win32", "cpu": "x64" }, "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg=="], + + "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="], + + "lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="], + + "lodash-es": ["lodash-es@4.17.21", "", {}, "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="], + + "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="], + + "lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], + + "magic-string": ["magic-string@0.30.17", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" } }, "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA=="], + + "memorystream": ["memorystream@0.3.1", "", {}, "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw=="], + + "merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="], + + "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], + + "minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], + + "minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="], + + "minizlib": ["minizlib@3.0.2", "", { "dependencies": { "minipass": "^7.1.2" } }, "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA=="], + + "mitt": ["mitt@3.0.1", "", {}, "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw=="], + + "mkdirp": ["mkdirp@3.0.1", "", { "bin": { "mkdirp": "dist/cjs/src/bin.js" } }, "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg=="], + + "mrmime": ["mrmime@2.0.1", "", {}, "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ=="], + + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "muggle-string": ["muggle-string@0.4.1", "", {}, "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ=="], + + "naive-ui": ["naive-ui@2.42.0", "", { "dependencies": { "@css-render/plugin-bem": "^0.15.14", "@css-render/vue3-ssr": "^0.15.14", "@types/katex": "^0.16.2", "@types/lodash": "^4.14.198", "@types/lodash-es": "^4.17.9", "async-validator": "^4.2.5", "css-render": "^0.15.14", "csstype": "^3.1.3", "date-fns": "^3.6.0", "date-fns-tz": "^3.1.3", "evtd": "^0.2.4", "highlight.js": "^11.8.0", "lodash": "^4.17.21", "lodash-es": "^4.17.21", "seemly": "^0.3.8", "treemate": "^0.3.11", "vdirs": "^0.1.8", "vooks": "^0.2.12", "vueuc": "^0.4.63" }, "peerDependencies": { "vue": "^3.0.0" } }, "sha512-c7cXR2YgOjgtBadXHwiWL4Y0tpGLAI5W5QzzHksOi22iuHXoSGMAzdkVTGVPE/PM0MSGQ/JtUIzCx2Y0hU0vTQ=="], + + "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], + + "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], + + "node-fetch": ["node-fetch@1.7.3", "", { "dependencies": { "encoding": "^0.1.11", "is-stream": "^1.0.1" } }, "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ=="], + + "node-releases": ["node-releases@2.0.19", "", {}, "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw=="], + + "npm-normalize-package-bin": ["npm-normalize-package-bin@4.0.0", "", {}, "sha512-TZKxPvItzai9kN9H/TkmCtx/ZN/hvr3vUycjlfmH0ootY9yFBzNOpiXAdIn1Iteqsvk4lQn6B5PTrt+n6h8k/w=="], + + "npm-run-all2": ["npm-run-all2@8.0.4", "", { "dependencies": { "ansi-styles": "^6.2.1", "cross-spawn": "^7.0.6", "memorystream": "^0.3.1", "picomatch": "^4.0.2", "pidtree": "^0.6.0", "read-package-json-fast": "^4.0.0", "shell-quote": "^1.7.3", "which": "^5.0.0" }, "bin": { "run-p": "bin/run-p/index.js", "run-s": "bin/run-s/index.js", "npm-run-all": "bin/npm-run-all/index.js", "npm-run-all2": "bin/npm-run-all/index.js" } }, "sha512-wdbB5My48XKp2ZfJUlhnLVihzeuA1hgBnqB2J9ahV77wLS+/YAJAlN8I+X3DIFIPZ3m5L7nplmlbhNiFDmXRDA=="], + + "npm-run-path": ["npm-run-path@6.0.0", "", { "dependencies": { "path-key": "^4.0.0", "unicorn-magic": "^0.3.0" } }, "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA=="], + + "nth-check": ["nth-check@2.1.1", "", { "dependencies": { "boolbase": "^1.0.0" } }, "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w=="], + + "open": ["open@10.2.0", "", { "dependencies": { "default-browser": "^5.2.1", "define-lazy-prop": "^3.0.0", "is-inside-container": "^1.0.0", "wsl-utils": "^0.1.0" } }, "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA=="], + + "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], + + "oxlint": ["oxlint@1.1.0", "", { "optionalDependencies": { "@oxlint/darwin-arm64": "1.1.0", "@oxlint/darwin-x64": "1.1.0", "@oxlint/linux-arm64-gnu": "1.1.0", "@oxlint/linux-arm64-musl": "1.1.0", "@oxlint/linux-x64-gnu": "1.1.0", "@oxlint/linux-x64-musl": "1.1.0", "@oxlint/win32-arm64": "1.1.0", "@oxlint/win32-x64": "1.1.0" }, "bin": { "oxlint": "bin/oxlint", "oxc_language_server": "bin/oxc_language_server" } }, "sha512-OVNpaoaQCUHHhCv5sYMPJ7Ts5k7ziw0QteH1gBSwF3elf/8GAew2Uh/0S7HsU1iGtjhlFy80+A8nwIb3Tq6m1w=="], + + "p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], + + "p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="], + + "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], + + "parse-ms": ["parse-ms@4.0.0", "", {}, "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw=="], + + "path-browserify": ["path-browserify@1.0.1", "", {}, "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g=="], + + "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], + + "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], + + "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], + + "perfect-debounce": ["perfect-debounce@1.0.0", "", {}, "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA=="], + + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], + + "picomatch": ["picomatch@4.0.2", "", {}, "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg=="], + + "pidtree": ["pidtree@0.6.0", "", { "bin": { "pidtree": "bin/pidtree.js" } }, "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g=="], + + "pinia": ["pinia@3.0.3", "", { "dependencies": { "@vue/devtools-api": "^7.7.2" }, "peerDependencies": { "typescript": ">=4.4.4", "vue": "^2.7.0 || ^3.5.11" }, "optionalPeers": ["typescript"] }, "sha512-ttXO/InUULUXkMHpTdp9Fj4hLpD/2AoJdmAbAeW2yu1iy1k+pkFekQXw5VpC0/5p51IOR/jDaDRfRWRnMMsGOA=="], + + "postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="], + + "postcss-selector-parser": ["postcss-selector-parser@6.1.2", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg=="], + + "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="], + + "prettier": ["prettier@3.5.3", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw=="], + + "prettier-linter-helpers": ["prettier-linter-helpers@1.0.0", "", { "dependencies": { "fast-diff": "^1.1.2" } }, "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w=="], + + "pretty-ms": ["pretty-ms@9.2.0", "", { "dependencies": { "parse-ms": "^4.0.0" } }, "sha512-4yf0QO/sllf/1zbZWYnvWw3NxCQwLXKzIj0G849LSufP15BXKM0rbD2Z3wVnkMfjdn/CB0Dpp444gYAACdsplg=="], + + "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], + + "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], + + "read-package-json-fast": ["read-package-json-fast@4.0.0", "", { "dependencies": { "json-parse-even-better-errors": "^4.0.0", "npm-normalize-package-bin": "^4.0.0" } }, "sha512-qpt8EwugBWDw2cgE2W+/3oxC+KTez2uSVR8JU9Q36TXPAGCaozfQUs59v4j4GFpWTaw0i6hAZSvOmu1J0uOEUg=="], + + "resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], + + "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], + + "rfdc": ["rfdc@1.4.1", "", {}, "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA=="], + + "rolldown": ["rolldown@1.0.0-beta.27", "", { "dependencies": { "@oxc-project/runtime": "=0.77.0", "@oxc-project/types": "=0.77.0", "@rolldown/pluginutils": "1.0.0-beta.27", "ansis": "^4.0.0" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.0.0-beta.27", "@rolldown/binding-darwin-arm64": "1.0.0-beta.27", "@rolldown/binding-darwin-x64": "1.0.0-beta.27", "@rolldown/binding-freebsd-x64": "1.0.0-beta.27", "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-beta.27", "@rolldown/binding-linux-arm64-gnu": "1.0.0-beta.27", "@rolldown/binding-linux-arm64-musl": "1.0.0-beta.27", "@rolldown/binding-linux-arm64-ohos": "1.0.0-beta.27", "@rolldown/binding-linux-x64-gnu": "1.0.0-beta.27", "@rolldown/binding-linux-x64-musl": "1.0.0-beta.27", "@rolldown/binding-wasm32-wasi": "1.0.0-beta.27", "@rolldown/binding-win32-arm64-msvc": "1.0.0-beta.27", "@rolldown/binding-win32-ia32-msvc": "1.0.0-beta.27", "@rolldown/binding-win32-x64-msvc": "1.0.0-beta.27" }, "bin": { "rolldown": "bin/cli.mjs" } }, "sha512-aYiJmzKoUHoaaEZLRegYVfZkXW7gzdgSbq+u5cXQ6iXc/y8tnQ3zGffQo44Pr1lTKeLluw3bDIDUCx/NAzqKeA=="], + + "run-applescript": ["run-applescript@7.0.0", "", {}, "sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A=="], + + "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], + + "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], + + "seemly": ["seemly@0.3.10", "", {}, "sha512-2+SMxtG1PcsL0uyhkumlOU6Qo9TAQ/WyH7tthnPIOQB05/12jz9naq6GZ6iZ6ApVsO3rr2gsnTf3++OV63kE1Q=="], + + "semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="], + + "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], + + "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], + + "shell-quote": ["shell-quote@1.8.3", "", {}, "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw=="], + + "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], + + "sirv": ["sirv@3.0.1", "", { "dependencies": { "@polka/url": "^1.0.0-next.24", "mrmime": "^2.0.0", "totalist": "^3.0.0" } }, "sha512-FoqMu0NCGBLCcAkS1qA+XJIQTR6/JHfQXl+uGteNCQ76T91DMUjPa9xfmeqMY3z80nLSg9yQmNjK0Px6RWsH/A=="], + + "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], + + "speakingurl": ["speakingurl@14.0.1", "", {}, "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ=="], + + "strip-final-newline": ["strip-final-newline@4.0.0", "", {}, "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw=="], + + "strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="], + + "superjson": ["superjson@2.2.2", "", { "dependencies": { "copy-anything": "^3.0.2" } }, "sha512-5JRxVqC8I8NuOUjzBbvVJAKNM8qoVuH0O77h4WInc/qC2q5IreqKxYwgkga3PfA22OayK2ikceb/B26dztPl+Q=="], + + "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], + + "synckit": ["synckit@0.11.8", "", { "dependencies": { "@pkgr/core": "^0.2.4" } }, "sha512-+XZ+r1XGIJGeQk3VvXhT6xx/VpbHsRzsTkGgF6E5RX9TTXD0118l87puaEBZ566FhqblC6U0d4XnubznJDm30A=="], + + "tailwindcss": ["tailwindcss@4.1.11", "", {}, "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA=="], + + "tapable": ["tapable@2.2.2", "", {}, "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg=="], + + "tar": ["tar@7.4.3", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.0.1", "mkdirp": "^3.0.1", "yallist": "^5.0.0" } }, "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw=="], + + "tinyglobby": ["tinyglobby@0.2.14", "", { "dependencies": { "fdir": "^6.4.4", "picomatch": "^4.0.2" } }, "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ=="], + + "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], + + "totalist": ["totalist@3.0.1", "", {}, "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ=="], + + "treemate": ["treemate@0.3.11", "", {}, "sha512-M8RGFoKtZ8dF+iwJfAJTOH/SM4KluKOKRJpjCMhI8bG3qB74zrFoArKZ62ll0Fr3mqkMJiQOmWYkdYgDeITYQg=="], + + "ts-api-utils": ["ts-api-utils@2.1.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ=="], + + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + + "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="], + + "typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="], + + "typescript-eslint": ["typescript-eslint@8.37.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.37.0", "@typescript-eslint/parser": "8.37.0", "@typescript-eslint/typescript-estree": "8.37.0", "@typescript-eslint/utils": "8.37.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-TnbEjzkE9EmcO0Q2zM+GE8NQLItNAJpMmED1BdgoBMYNdqMhzlbqfdSwiRlAzEK2pA9UzVW0gzaaIzXWg2BjfA=="], + + "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], + + "unicorn-magic": ["unicorn-magic@0.3.0", "", {}, "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA=="], + + "universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="], + + "update-browserslist-db": ["update-browserslist-db@1.1.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw=="], + + "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], + + "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], + + "vdirs": ["vdirs@0.1.8", "", { "dependencies": { "evtd": "^0.2.2" }, "peerDependencies": { "vue": "^3.0.11" } }, "sha512-H9V1zGRLQZg9b+GdMk8MXDN2Lva0zx72MPahDKc30v+DtwKjfyOSXWRIX4t2mhDubM1H09gPhWeth/BJWPHGUw=="], + + "vite": ["rolldown-vite@7.0.9", "", { "dependencies": { "fdir": "^6.4.6", "lightningcss": "^1.30.1", "picomatch": "^4.0.2", "postcss": "^8.5.6", "rolldown": "1.0.0-beta.27", "tinyglobby": "^0.2.14" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "esbuild": "^0.25.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-RxVP6CY9CNCEM9UecdytqeADxOGSjgkfSE/eI986sM7I3/F09lQ9UfQo3y6W10ICBppKsEHe71NbCX/tirYDFg=="], + + "vite-hot-client": ["vite-hot-client@2.1.0", "", { "peerDependencies": { "vite": "^2.6.0 || ^3.0.0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0" } }, "sha512-7SpgZmU7R+dDnSmvXE1mfDtnHLHQSisdySVR7lO8ceAXvM0otZeuQQ6C8LrS5d/aYyP/QZ0hI0L+dIPrm4YlFQ=="], + + "vite-plugin-inspect": ["vite-plugin-inspect@0.8.9", "", { "dependencies": { "@antfu/utils": "^0.7.10", "@rollup/pluginutils": "^5.1.3", "debug": "^4.3.7", "error-stack-parser-es": "^0.1.5", "fs-extra": "^11.2.0", "open": "^10.1.0", "perfect-debounce": "^1.0.0", "picocolors": "^1.1.1", "sirv": "^3.0.0" }, "peerDependencies": { "vite": "^3.1.0 || ^4.0.0 || ^5.0.0-0 || ^6.0.1" } }, "sha512-22/8qn+LYonzibb1VeFZmISdVao5kC22jmEKm24vfFE8siEn47EpVcCLYMv6iKOYMJfjSvSJfueOwcFCkUnV3A=="], + + "vite-plugin-vue-devtools": ["vite-plugin-vue-devtools@7.7.7", "", { "dependencies": { "@vue/devtools-core": "^7.7.7", "@vue/devtools-kit": "^7.7.7", "@vue/devtools-shared": "^7.7.7", "execa": "^9.5.2", "sirv": "^3.0.1", "vite-plugin-inspect": "0.8.9", "vite-plugin-vue-inspector": "^5.3.1" }, "peerDependencies": { "vite": "^3.1.0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0" } }, "sha512-d0fIh3wRcgSlr4Vz7bAk4va1MkdqhQgj9ANE/rBhsAjOnRfTLs2ocjFMvSUOsv6SRRXU9G+VM7yMgqDb6yI4iQ=="], + + "vite-plugin-vue-inspector": ["vite-plugin-vue-inspector@5.3.2", "", { "dependencies": { "@babel/core": "^7.23.0", "@babel/plugin-proposal-decorators": "^7.23.0", "@babel/plugin-syntax-import-attributes": "^7.22.5", "@babel/plugin-syntax-import-meta": "^7.10.4", "@babel/plugin-transform-typescript": "^7.22.15", "@vue/babel-plugin-jsx": "^1.1.5", "@vue/compiler-dom": "^3.3.4", "kolorist": "^1.8.0", "magic-string": "^0.30.4" }, "peerDependencies": { "vite": "^3.0.0-0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0" } }, "sha512-YvEKooQcSiBTAs0DoYLfefNja9bLgkFM7NI2b07bE2SruuvX0MEa9cMaxjKVMkeCp5Nz9FRIdcN1rOdFVBeL6Q=="], + + "vooks": ["vooks@0.2.12", "", { "dependencies": { "evtd": "^0.2.2" }, "peerDependencies": { "vue": "^3.0.0" } }, "sha512-iox0I3RZzxtKlcgYaStQYKEzWWGAduMmq+jS7OrNdQo1FgGfPMubGL3uGHOU9n97NIvfFDBGnpSvkWyb/NSn/Q=="], + + "vscode-uri": ["vscode-uri@3.1.0", "", {}, "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ=="], + + "vue": ["vue@3.5.17", "", { "dependencies": { "@vue/compiler-dom": "3.5.17", "@vue/compiler-sfc": "3.5.17", "@vue/runtime-dom": "3.5.17", "@vue/server-renderer": "3.5.17", "@vue/shared": "3.5.17" }, "peerDependencies": { "typescript": "*" }, "optionalPeers": ["typescript"] }, "sha512-LbHV3xPN9BeljML+Xctq4lbz2lVHCR6DtbpTf5XIO6gugpXUN49j2QQPcMj086r9+AkJ0FfUT8xjulKKBkkr9g=="], + + "vue-eslint-parser": ["vue-eslint-parser@10.2.0", "", { "dependencies": { "debug": "^4.4.0", "eslint-scope": "^8.2.0", "eslint-visitor-keys": "^4.2.0", "espree": "^10.3.0", "esquery": "^1.6.0", "semver": "^7.6.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0" } }, "sha512-CydUvFOQKD928UzZhTp4pr2vWz1L+H99t7Pkln2QSPdvmURT0MoC4wUccfCnuEaihNsu9aYYyk+bep8rlfkUXw=="], + + "vue-router": ["vue-router@4.5.1", "", { "dependencies": { "@vue/devtools-api": "^6.6.4" }, "peerDependencies": { "vue": "^3.2.0" } }, "sha512-ogAF3P97NPm8fJsE4by9dwSYtDwXIY1nFY9T6DyQnGHd1E2Da94w9JIolpe42LJGIl0DwOHBi8TcRPlPGwbTtw=="], + + "vue-tsc": ["vue-tsc@2.2.12", "", { "dependencies": { "@volar/typescript": "2.4.15", "@vue/language-core": "2.2.12" }, "peerDependencies": { "typescript": ">=5.0.0" }, "bin": { "vue-tsc": "./bin/vue-tsc.js" } }, "sha512-P7OP77b2h/Pmk+lZdJ0YWs+5tJ6J2+uOQPo7tlBnY44QqQSPYvS0qVT4wqDJgwrZaLe47etJLLQRFia71GYITw=="], + + "vueuc": ["vueuc@0.4.64", "", { "dependencies": { "@css-render/vue3-ssr": "^0.15.10", "@juggle/resize-observer": "^3.3.1", "css-render": "^0.15.10", "evtd": "^0.2.4", "seemly": "^0.3.6", "vdirs": "^0.1.4", "vooks": "^0.2.4" }, "peerDependencies": { "vue": "^3.0.11" } }, "sha512-wlJQj7fIwKK2pOEoOq4Aro8JdPOGpX8aWQhV8YkTW9OgWD2uj2O8ANzvSsIGjx7LTOc7QbS7sXdxHi6XvRnHPA=="], + + "whatwg-fetch": ["whatwg-fetch@3.6.20", "", {}, "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg=="], + + "which": ["which@5.0.0", "", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ=="], + + "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], + + "wsl-utils": ["wsl-utils@0.1.0", "", { "dependencies": { "is-wsl": "^3.1.0" } }, "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw=="], + + "xml-name-validator": ["xml-name-validator@4.0.0", "", {}, "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw=="], + + "yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="], + + "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], + + "yoctocolors": ["yoctocolors@2.1.1", "", {}, "sha512-GQHQqAopRhwU8Kt1DDM8NjibDXHC8eoh1erhGAJPEyveY9qqVeXvVikNKrDz69sHowPMorbPUrH/mx8c50eiBQ=="], + + "@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + + "@babel/helper-compilation-targets/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + + "@babel/helper-create-class-features-plugin/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + + "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], + + "@humanfs/node/@humanwhocodes/retry": ["@humanwhocodes/retry@0.3.1", "", {}, "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA=="], + + "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.4.4", "", { "dependencies": { "@emnapi/wasi-threads": "1.0.3", "tslib": "^2.4.0" }, "bundled": true }, "sha512-A9CnAbC6ARNMKcIcrQwq6HeHCjpcBZ5wSx4U01WXCqEKlrzB9F9315WDNHkrs2xbx7YjjSxbUYxuN6EQzpcY2g=="], + + "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.4.4", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-hHyapA4A3gPaDCNfiqyZUStTMqIkKRshqPIuDOXv1hcBnD4U3l8cP0T1HMCfGRxQ6V64TGCcoswChANyOAwbQg=="], + + "@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.0.3", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-8K5IFFsQqF9wQNJptGbS6FNKgUTsSRYnTqNCG1vPP8jFdjSv18n2mQfJpkt2Oibo9iBEzcDnDxNwKTzC7svlJw=="], + + "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@0.2.12", "", { "dependencies": { "@emnapi/core": "^1.4.3", "@emnapi/runtime": "^1.4.3", "@tybys/wasm-util": "^0.10.0" }, "bundled": true }, "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ=="], + + "@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.9.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw=="], + + "@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + + "@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], + + "@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], + + "@vitejs/plugin-vue-jsx/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.9-commit.d91dfb5", "", {}, "sha512-8sExkWRK+zVybw3+2/kBkYBFeLnEUWz1fT7BLHplpzmtqkOfTbAQ9gkt4pzwGIIZmg4Qn5US5ACjUBenrhezwQ=="], + + "@vue/devtools-core/nanoid": ["nanoid@5.1.5", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw=="], + + "@vue/language-core/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], + + "chalk/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + + "cross-spawn/which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], + + "css-render/csstype": ["csstype@3.0.11", "", {}, "sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw=="], + + "fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], + + "lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], + + "micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], + + "node-fetch/is-stream": ["is-stream@1.1.0", "", {}, "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ=="], + + "npm-run-path/path-key": ["path-key@4.0.0", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="], + + "rolldown/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.27", "", {}, "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA=="], + + "vue-router/@vue/devtools-api": ["@vue/devtools-api@6.6.4", "", {}, "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g=="], + + "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime/@tybys/wasm-util": ["@tybys/wasm-util@0.10.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-VyyPYFlOMNylG45GoAe0xDoLwWuowvf92F9kySqzYh8vmYm7D2u4iUJKa1tOUpS70Ku13ASrOkS4ScXFsTaCNQ=="], + + "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], + + "@vue/language-core/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], + + "cross-spawn/which/isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + } +} diff --git a/DysonNetwork.Pass/Client/env.d.ts b/DysonNetwork.Pass/Client/env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/DysonNetwork.Pass/Client/env.d.ts @@ -0,0 +1 @@ +/// diff --git a/DysonNetwork.Pass/Client/eslint.config.ts b/DysonNetwork.Pass/Client/eslint.config.ts new file mode 100644 index 0000000..88dcb9a --- /dev/null +++ b/DysonNetwork.Pass/Client/eslint.config.ts @@ -0,0 +1,29 @@ +import { globalIgnores } from 'eslint/config' +import { defineConfigWithVueTs, vueTsConfigs } from '@vue/eslint-config-typescript' +import pluginVue from 'eslint-plugin-vue' +import pluginOxlint from 'eslint-plugin-oxlint' +import skipFormatting from '@vue/eslint-config-prettier/skip-formatting' + +// To allow more languages other than `ts` in `.vue` files, uncomment the following lines: +// import { configureVueProject } from '@vue/eslint-config-typescript' +// configureVueProject({ scriptLangs: ['ts', 'tsx'] }) +// More info at https://github.com/vuejs/eslint-config-typescript/#advanced-setup + +export default defineConfigWithVueTs( + { + name: 'app/files-to-lint', + files: ['**/*.{ts,mts,tsx,vue}'], + }, + + globalIgnores(['**/dist/**', '**/dist-ssr/**', '**/coverage/**']), + + pluginVue.configs['flat/essential'], + vueTsConfigs.recommended, + ...pluginOxlint.configs['flat/recommended'], + { + rules: { + 'vue/multi-word-component-names': 'off', + }, + }, + skipFormatting, +) diff --git a/DysonNetwork.Pass/Client/index.html b/DysonNetwork.Pass/Client/index.html new file mode 100644 index 0000000..a5ca9ee --- /dev/null +++ b/DysonNetwork.Pass/Client/index.html @@ -0,0 +1,13 @@ + + + + + + + Solarpass + + +
+ + + diff --git a/DysonNetwork.Pass/Client/package-lock.json b/DysonNetwork.Pass/Client/package-lock.json new file mode 100644 index 0000000..a035ebd --- /dev/null +++ b/DysonNetwork.Pass/Client/package-lock.json @@ -0,0 +1,4523 @@ +{ + "name": "@solar-network/pass", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@solar-network/pass", + "version": "0.0.0", + "dependencies": { + "@fontsource-variable/nunito": "^5.2.6", + "@tailwindcss/vite": "^4.1.11", + "aspnet-prerendering": "^3.0.1", + "pinia": "^3.0.3", + "tailwindcss": "^4.1.11", + "vue": "^3.5.17", + "vue-router": "^4.5.1" + }, + "devDependencies": { + "@tsconfig/node22": "^22.0.2", + "@types/node": "^22.16.4", + "@vicons/material": "^0.13.0", + "@vitejs/plugin-vue": "^6.0.0", + "@vitejs/plugin-vue-jsx": "^5.0.1", + "@vue/eslint-config-prettier": "^10.2.0", + "@vue/eslint-config-typescript": "^14.6.0", + "@vue/tsconfig": "^0.7.0", + "eslint": "^9.31.0", + "eslint-plugin-oxlint": "~1.1.0", + "eslint-plugin-vue": "~10.2.0", + "jiti": "^2.4.2", + "naive-ui": "^2.42.0", + "npm-run-all2": "^8.0.4", + "oxlint": "~1.1.0", + "prettier": "3.5.3", + "typescript": "~5.8.3", + "vite": "npm:rolldown-vite@latest", + "vite-plugin-vue-devtools": "^7.7.7", + "vue-tsc": "^2.2.12" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@antfu/utils": { + "version": "0.7.10", + "resolved": "https://registry.npmjs.org/@antfu/utils/-/utils-0.7.10.tgz", + "integrity": "sha512-+562v9k4aI80m1+VuMHehNJWLOFjBnXn3tdOitzD0il5b7smkSBal4+a3oKiQTbrwMmN/TBUMDvbdoWDehgOww==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.28.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.0", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.27.3", + "@babel/helpers": "^7.27.6", + "@babel/parser": "^7.28.0", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.0", + "@babel/types": "^7.28.0", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.28.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.0", + "@babel/types": "^7.28.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.27.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.3" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.27.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-member-expression-to-functions": "^7.27.1", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/traverse": "^7.27.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { + "version": "6.3.1", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.27.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.27.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.27.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.27.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.27.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.27.1", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.27.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.27.1", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.27.6", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/types": "^7.27.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.0", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-proposal-decorators": { + "version": "7.28.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/plugin-syntax-decorators": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-decorators": { + "version": "7.27.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.27.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.27.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.27.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typescript": { + "version": "7.28.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/plugin-syntax-typescript": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.28.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.0", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.1", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@css-render/plugin-bem": { + "version": "0.15.14", + "resolved": "https://registry.npmjs.org/@css-render/plugin-bem/-/plugin-bem-0.15.14.tgz", + "integrity": "sha512-QK513CJ7yEQxm/P3EwsI+d+ha8kSOcjGvD6SevM41neEMxdULE+18iuQK6tEChAWMOQNQPLG/Rw3Khb69r5neg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "css-render": "~0.15.14" + } + }, + "node_modules/@css-render/vue3-ssr": { + "version": "0.15.14", + "resolved": "https://registry.npmjs.org/@css-render/vue3-ssr/-/vue3-ssr-0.15.14.tgz", + "integrity": "sha512-//8027GSbxE9n3QlD73xFY6z4ZbHbvrOVB7AO6hsmrEzGbg+h2A09HboUyDgu+xsmj7JnvJD39Irt+2D0+iV8g==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "vue": "^3.0.11" + } + }, + "node_modules/@emotion/hash": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz", + "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.0", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.6", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.3.0", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.15.1", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/js": { + "version": "9.31.0", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.6", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.3.3", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.15.1", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@fontsource-variable/nunito": { + "version": "5.2.6", + "resolved": "https://registry.npmjs.org/@fontsource-variable/nunito/-/nunito-5.2.6.tgz", + "integrity": "sha512-dGYTQ0Hl94jjfMraYefrURHGH8fk/vL/1zYAZGofiPJVs6C0OkM8T87Te5Gwrbe6HG/XEMm5lib8AqasTN3ucw==", + "license": "OFL-1.1", + "funding": { + "url": "https://github.com/sponsors/ayuhito" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.6", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.3.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { + "version": "0.3.1", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.12", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.4", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.29", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@juggle/resize-observer": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@juggle/resize-observer/-/resize-observer-3.4.0.tgz", + "integrity": "sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@oxc-project/runtime": { + "version": "0.77.0", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.77.0", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@oxlint/darwin-arm64": { + "version": "1.1.0", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@pkgr/core": { + "version": "0.2.7", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/pkgr" + } + }, + "node_modules/@polka/url": { + "version": "1.0.0-next.29", + "dev": true, + "license": "MIT" + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.0-beta.27", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.19", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/pluginutils": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.2.0.tgz", + "integrity": "sha512-qWJ2ZTbmumwiLFomfzTyt5Kng4hwPi9rwCYN4SHb6eaRU1KNO4ccxINHr/VhH4GgPlt1XfSTLX2LBTme8ne4Zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@sec-ant/readable-stream": { + "version": "0.4.1", + "dev": true, + "license": "MIT" + }, + "node_modules/@sindresorhus/merge-streams": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@tailwindcss/node": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.11.tgz", + "integrity": "sha512-yzhzuGRmv5QyU9qLNg4GTlYI6STedBWRE7NjxP45CsFYYq9taI0zJXZBMqIC/c8fViNLhmrbpSFS57EoxUmD6Q==", + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.3.0", + "enhanced-resolve": "^5.18.1", + "jiti": "^2.4.2", + "lightningcss": "1.30.1", + "magic-string": "^0.30.17", + "source-map-js": "^1.2.1", + "tailwindcss": "4.1.11" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.11.tgz", + "integrity": "sha512-Q69XzrtAhuyfHo+5/HMgr1lAiPP/G40OMFAnws7xcFEYqcypZmdW8eGXaOUIeOl1dzPJBPENXgbjsOyhg2nkrg==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.4", + "tar": "^7.4.3" + }, + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.1.11", + "@tailwindcss/oxide-darwin-arm64": "4.1.11", + "@tailwindcss/oxide-darwin-x64": "4.1.11", + "@tailwindcss/oxide-freebsd-x64": "4.1.11", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.11", + "@tailwindcss/oxide-linux-arm64-gnu": "4.1.11", + "@tailwindcss/oxide-linux-arm64-musl": "4.1.11", + "@tailwindcss/oxide-linux-x64-gnu": "4.1.11", + "@tailwindcss/oxide-linux-x64-musl": "4.1.11", + "@tailwindcss/oxide-wasm32-wasi": "4.1.11", + "@tailwindcss/oxide-win32-arm64-msvc": "4.1.11", + "@tailwindcss/oxide-win32-x64-msvc": "4.1.11" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.11.tgz", + "integrity": "sha512-3IfFuATVRUMZZprEIx9OGDjG3Ou3jG4xQzNTvjDoKmU9JdmoCohQJ83MYd0GPnQIu89YoJqvMM0G3uqLRFtetg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.11.tgz", + "integrity": "sha512-ESgStEOEsyg8J5YcMb1xl8WFOXfeBmrhAwGsFxxB2CxY9evy63+AtpbDLAyRkJnxLy2WsD1qF13E97uQyP1lfQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.11.tgz", + "integrity": "sha512-EgnK8kRchgmgzG6jE10UQNaH9Mwi2n+yw1jWmof9Vyg2lpKNX2ioe7CJdf9M5f8V9uaQxInenZkOxnTVL3fhAw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.11.tgz", + "integrity": "sha512-xdqKtbpHs7pQhIKmqVpxStnY1skuNh4CtbcyOHeX1YBE0hArj2romsFGb6yUmzkq/6M24nkxDqU8GYrKrz+UcA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.11.tgz", + "integrity": "sha512-ryHQK2eyDYYMwB5wZL46uoxz2zzDZsFBwfjssgB7pzytAeCCa6glsiJGjhTEddq/4OsIjsLNMAiMlHNYnkEEeg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.11.tgz", + "integrity": "sha512-mYwqheq4BXF83j/w75ewkPJmPZIqqP1nhoghS9D57CLjsh3Nfq0m4ftTotRYtGnZd3eCztgbSPJ9QhfC91gDZQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.11.tgz", + "integrity": "sha512-m/NVRFNGlEHJrNVk3O6I9ggVuNjXHIPoD6bqay/pubtYC9QIdAMpS+cswZQPBLvVvEF6GtSNONbDkZrjWZXYNQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.11.tgz", + "integrity": "sha512-YW6sblI7xukSD2TdbbaeQVDysIm/UPJtObHJHKxDEcW2exAtY47j52f8jZXkqE1krdnkhCMGqP3dbniu1Te2Fg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.11.tgz", + "integrity": "sha512-e3C/RRhGunWYNC3aSF7exsQkdXzQ/M+aYuZHKnw4U7KQwTJotnWsGOIVih0s2qQzmEzOFIJ3+xt7iq67K/p56Q==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.11.tgz", + "integrity": "sha512-Xo1+/GU0JEN/C/dvcammKHzeM6NqKovG+6921MR6oadee5XPBaKOumrJCXvopJ/Qb5TH7LX/UAywbqrP4lax0g==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.4.3", + "@emnapi/runtime": "^1.4.3", + "@emnapi/wasi-threads": "^1.0.2", + "@napi-rs/wasm-runtime": "^0.2.11", + "@tybys/wasm-util": "^0.9.0", + "tslib": "^2.8.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.11.tgz", + "integrity": "sha512-UgKYx5PwEKrac3GPNPf6HVMNhUIGuUh4wlDFR2jYYdkX6pL/rn73zTq/4pzUm8fOjAn5L8zDeHp9iXmUGOXZ+w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.11.tgz", + "integrity": "sha512-YfHoggn1j0LK7wR82TOucWc5LDCguHnoS879idHekmmiR7g9HUtMw9MI0NHatS28u/Xlkfi9w5RJWgz2Dl+5Qg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/vite": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.11.tgz", + "integrity": "sha512-RHYhrR3hku0MJFRV+fN2gNbDNEh3dwKvY8XJvTxCSXeMOsCRSr+uKvDWQcbizrHgjML6ZmTE5OwMrl5wKcujCw==", + "license": "MIT", + "dependencies": { + "@tailwindcss/node": "4.1.11", + "@tailwindcss/oxide": "4.1.11", + "tailwindcss": "4.1.11" + }, + "peerDependencies": { + "vite": "^5.2.0 || ^6 || ^7" + } + }, + "node_modules/@tsconfig/node22": { + "version": "22.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/katex": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@types/katex/-/katex-0.16.7.tgz", + "integrity": "sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/lodash": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/lodash-es": { + "version": "4.17.12", + "resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.12.tgz", + "integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/lodash": "*" + } + }, + "node_modules/@types/node": { + "version": "22.16.4", + "devOptional": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.37.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.37.0", + "@typescript-eslint/type-utils": "8.37.0", + "@typescript-eslint/utils": "8.37.0", + "@typescript-eslint/visitor-keys": "8.37.0", + "graphemer": "^1.4.0", + "ignore": "^7.0.0", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.37.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.37.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.37.0", + "@typescript-eslint/types": "8.37.0", + "@typescript-eslint/typescript-estree": "8.37.0", + "@typescript-eslint/visitor-keys": "8.37.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.37.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.37.0", + "@typescript-eslint/types": "^8.37.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.37.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.37.0", + "@typescript-eslint/visitor-keys": "8.37.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.37.0", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.37.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.37.0", + "@typescript-eslint/typescript-estree": "8.37.0", + "@typescript-eslint/utils": "8.37.0", + "debug": "^4.3.4", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.37.0", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.37.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.37.0", + "@typescript-eslint/tsconfig-utils": "8.37.0", + "@typescript-eslint/types": "8.37.0", + "@typescript-eslint/visitor-keys": "8.37.0", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.5", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch/node_modules/brace-expansion": { + "version": "2.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.37.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.7.0", + "@typescript-eslint/scope-manager": "8.37.0", + "@typescript-eslint/types": "8.37.0", + "@typescript-eslint/typescript-estree": "8.37.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.37.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.37.0", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@vicons/material": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@vicons/material/-/material-0.13.0.tgz", + "integrity": "sha512-lKVxFNprM+CaBkUH3gt6VjIeiMsKQl2zARQMwTCZruQl2vRHzyeZiKeCflWS99CEfv2JzX/6y697smxlzyxcVw==", + "dev": true, + "license": "Apache 2.0" + }, + "node_modules/@vitejs/plugin-vue": { + "version": "6.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@rolldown/pluginutils": "1.0.0-beta.19" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@vitejs/plugin-vue-jsx": { + "version": "5.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.27.7", + "@babel/plugin-transform-typescript": "^7.27.1", + "@rolldown/pluginutils": "^1.0.0-beta.21", + "@vue/babel-plugin-jsx": "^1.4.0" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0", + "vue": "^3.0.0" + } + }, + "node_modules/@vitejs/plugin-vue-jsx/node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.9-commit.d91dfb5", + "dev": true, + "license": "MIT" + }, + "node_modules/@volar/language-core": { + "version": "2.4.15", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/source-map": "2.4.15" + } + }, + "node_modules/@volar/source-map": { + "version": "2.4.15", + "dev": true, + "license": "MIT" + }, + "node_modules/@volar/typescript": { + "version": "2.4.15", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.15", + "path-browserify": "^1.0.1", + "vscode-uri": "^3.0.8" + } + }, + "node_modules/@vue/babel-helper-vue-transform-on": { + "version": "1.4.0", + "dev": true, + "license": "MIT" + }, + "node_modules/@vue/babel-plugin-jsx": { + "version": "1.4.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-plugin-utils": "^7.26.5", + "@babel/plugin-syntax-jsx": "^7.25.9", + "@babel/template": "^7.26.9", + "@babel/traverse": "^7.26.9", + "@babel/types": "^7.26.9", + "@vue/babel-helper-vue-transform-on": "1.4.0", + "@vue/babel-plugin-resolve-type": "1.4.0", + "@vue/shared": "^3.5.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + } + } + }, + "node_modules/@vue/babel-plugin-resolve-type": { + "version": "1.4.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.26.2", + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-plugin-utils": "^7.26.5", + "@babel/parser": "^7.26.9", + "@vue/compiler-sfc": "^3.5.13" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.17", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.27.5", + "@vue/shared": "3.5.17", + "entities": "^4.5.0", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.17", + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.17", + "@vue/shared": "3.5.17" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.17", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.27.5", + "@vue/compiler-core": "3.5.17", + "@vue/compiler-dom": "3.5.17", + "@vue/compiler-ssr": "3.5.17", + "@vue/shared": "3.5.17", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.17", + "postcss": "^8.5.6", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.17", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.17", + "@vue/shared": "3.5.17" + } + }, + "node_modules/@vue/compiler-vue2": { + "version": "2.7.16", + "dev": true, + "license": "MIT", + "dependencies": { + "de-indent": "^1.0.2", + "he": "^1.2.0" + } + }, + "node_modules/@vue/devtools-api": { + "version": "7.7.7", + "license": "MIT", + "dependencies": { + "@vue/devtools-kit": "^7.7.7" + } + }, + "node_modules/@vue/devtools-core": { + "version": "7.7.7", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/devtools-kit": "^7.7.7", + "@vue/devtools-shared": "^7.7.7", + "mitt": "^3.0.1", + "nanoid": "^5.1.0", + "pathe": "^2.0.3", + "vite-hot-client": "^2.0.4" + }, + "peerDependencies": { + "vue": "^3.0.0" + } + }, + "node_modules/@vue/devtools-core/node_modules/nanoid": { + "version": "5.1.5", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^18 || >=20" + } + }, + "node_modules/@vue/devtools-kit": { + "version": "7.7.7", + "license": "MIT", + "dependencies": { + "@vue/devtools-shared": "^7.7.7", + "birpc": "^2.3.0", + "hookable": "^5.5.3", + "mitt": "^3.0.1", + "perfect-debounce": "^1.0.0", + "speakingurl": "^14.0.1", + "superjson": "^2.2.2" + } + }, + "node_modules/@vue/devtools-shared": { + "version": "7.7.7", + "license": "MIT", + "dependencies": { + "rfdc": "^1.4.1" + } + }, + "node_modules/@vue/eslint-config-prettier": { + "version": "10.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-config-prettier": "^10.0.1", + "eslint-plugin-prettier": "^5.2.2" + }, + "peerDependencies": { + "eslint": ">= 8.21.0", + "prettier": ">= 3.0.0" + } + }, + "node_modules/@vue/eslint-config-typescript": { + "version": "14.6.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/utils": "^8.35.1", + "fast-glob": "^3.3.3", + "typescript-eslint": "^8.35.1", + "vue-eslint-parser": "^10.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "peerDependencies": { + "eslint": "^9.10.0", + "eslint-plugin-vue": "^9.28.0 || ^10.0.0", + "typescript": ">=4.8.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@vue/language-core": { + "version": "2.2.12", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.15", + "@vue/compiler-dom": "^3.5.0", + "@vue/compiler-vue2": "^2.7.16", + "@vue/shared": "^3.5.0", + "alien-signals": "^1.0.3", + "minimatch": "^9.0.3", + "muggle-string": "^0.4.1", + "path-browserify": "^1.0.1" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@vue/language-core/node_modules/minimatch": { + "version": "9.0.5", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@vue/language-core/node_modules/minimatch/node_modules/brace-expansion": { + "version": "2.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@vue/reactivity": { + "version": "3.5.17", + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.17" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.17", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.17", + "@vue/shared": "3.5.17" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.17", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.17", + "@vue/runtime-core": "3.5.17", + "@vue/shared": "3.5.17", + "csstype": "^3.1.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.17", + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.17", + "@vue/shared": "3.5.17" + }, + "peerDependencies": { + "vue": "3.5.17" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.17", + "license": "MIT" + }, + "node_modules/@vue/tsconfig": { + "version": "0.7.0", + "dev": true, + "license": "MIT", + "peerDependencies": { + "typescript": "5.x", + "vue": "^3.4.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + }, + "vue": { + "optional": true + } + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/alien-signals": { + "version": "1.0.13", + "dev": true, + "license": "MIT" + }, + "node_modules/ansi-styles": { + "version": "6.2.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/ansis": { + "version": "4.1.0", + "license": "ISC", + "engines": { + "node": ">=14" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/aspnet-prerendering": { + "version": "3.0.1", + "license": "Apache-2.0", + "dependencies": { + "domain-task": "^3.0.0" + } + }, + "node_modules/async-validator": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/async-validator/-/async-validator-4.2.5.tgz", + "integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/birpc": { + "version": "2.5.0", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.25.1", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "caniuse-lite": "^1.0.30001726", + "electron-to-chromium": "^1.5.173", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001727", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/ansi-styles": { + "version": "4.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/copy-anything": { + "version": "3.0.5", + "license": "MIT", + "dependencies": { + "is-what": "^4.1.8" + }, + "engines": { + "node": ">=12.13" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cross-spawn/node_modules/which": { + "version": "2.0.2", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cross-spawn/node_modules/which/node_modules/isexe": { + "version": "2.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/css-render": { + "version": "0.15.14", + "resolved": "https://registry.npmjs.org/css-render/-/css-render-0.15.14.tgz", + "integrity": "sha512-9nF4PdUle+5ta4W5SyZdLCCmFd37uVimSjg1evcTqKJCyvCEEj12WKzOSBNak6r4im4J4iYXKH1OWpUV5LBYFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@emotion/hash": "~0.8.0", + "csstype": "~3.0.5" + } + }, + "node_modules/css-render/node_modules/csstype": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.11.tgz", + "integrity": "sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw==", + "dev": true, + "license": "MIT" + }, + "node_modules/cssesc": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "license": "MIT" + }, + "node_modules/date-fns": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz", + "integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, + "node_modules/date-fns-tz": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/date-fns-tz/-/date-fns-tz-3.2.0.tgz", + "integrity": "sha512-sg8HqoTEulcbbbVXeg84u5UnlsQa8GS5QXMqjjYIhS4abEVVKIUwe0/l/UhrZdKaL/W5eWZNlbTeEIiOXTcsBQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "date-fns": "^3.0.0 || ^4.0.0" + } + }, + "node_modules/de-indent": { + "version": "1.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.1", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "dev": true, + "license": "MIT" + }, + "node_modules/default-browser": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.2.1.tgz", + "integrity": "sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==", + "dev": true, + "license": "MIT", + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.0.tgz", + "integrity": "sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/detect-libc": { + "version": "2.0.4", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/domain-context": { + "version": "0.5.1", + "license": "MIT" + }, + "node_modules/domain-task": { + "version": "3.0.3", + "license": "Apache-2.0", + "dependencies": { + "domain-context": "^0.5.1", + "is-absolute-url": "^2.1.0", + "isomorphic-fetch": "^2.2.1" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.183", + "dev": true, + "license": "ISC" + }, + "node_modules/encoding": { + "version": "0.1.13", + "license": "MIT", + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.18.2", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.2.tgz", + "integrity": "sha512-6Jw4sE1maoRJo3q8MsSIn2onJFbLTOjY9hlx4DZXmOKvLRd1Ok2kXmAGXaafL2+ijsJZ1ClYbl/pmqr9+k4iUQ==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/error-stack-parser-es": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/error-stack-parser-es/-/error-stack-parser-es-0.1.5.tgz", + "integrity": "sha512-xHku1X40RO+fO8yJ8Wh2f2rZWVjqyhb1zgq1yZ8aZRQkv6OOKhKWRUaht3eSCUbAOBaKIgM+ykwFLE+QUxgGeg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.31.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.0", + "@eslint/config-helpers": "^0.3.0", + "@eslint/core": "^0.15.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.31.0", + "@eslint/plugin-kit": "^0.3.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-config-prettier": { + "version": "10.1.5", + "dev": true, + "license": "MIT", + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "funding": { + "url": "https://opencollective.com/eslint-config-prettier" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-oxlint": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "jsonc-parser": "^3.3.1" + } + }, + "node_modules/eslint-plugin-prettier": { + "version": "5.5.1", + "dev": true, + "license": "MIT", + "dependencies": { + "prettier-linter-helpers": "^1.0.0", + "synckit": "^0.11.7" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-plugin-prettier" + }, + "peerDependencies": { + "@types/eslint": ">=8.0.0", + "eslint": ">=8.0.0", + "eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0", + "prettier": ">=3.0.0" + }, + "peerDependenciesMeta": { + "@types/eslint": { + "optional": true + }, + "eslint-config-prettier": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-vue": { + "version": "10.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "natural-compare": "^1.4.0", + "nth-check": "^2.1.1", + "postcss-selector-parser": "^6.0.15", + "semver": "^7.6.3", + "xml-name-validator": "^4.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "vue-eslint-parser": "^10.0.0" + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "license": "MIT" + }, + "node_modules/esutils": { + "version": "2.0.3", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/evtd": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/evtd/-/evtd-0.2.4.tgz", + "integrity": "sha512-qaeGN5bx63s/AXgQo8gj6fBkxge+OoLddLniox5qtLAEY5HSnuSlISXVPxnSae1dWblvTh4/HoMIB+mbMsvZzw==", + "dev": true, + "license": "MIT" + }, + "node_modules/execa": { + "version": "9.6.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/merge-streams": "^4.0.0", + "cross-spawn": "^7.0.6", + "figures": "^6.1.0", + "get-stream": "^9.0.0", + "human-signals": "^8.0.1", + "is-plain-obj": "^4.1.0", + "is-stream": "^4.0.1", + "npm-run-path": "^6.0.0", + "pretty-ms": "^9.2.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^4.0.0", + "yoctocolors": "^2.1.1" + }, + "engines": { + "node": "^18.19.0 || >=20.5.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-diff": { + "version": "1.3.0", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "dev": true, + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.19.1", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fdir": { + "version": "6.4.6", + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/figures": { + "version": "6.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "is-unicode-supported": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "dev": true, + "license": "ISC" + }, + "node_modules/fs-extra": { + "version": "11.3.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.0.tgz", + "integrity": "sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-stream": { + "version": "9.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@sec-ant/readable-stream": "^0.4.1", + "is-stream": "^4.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "14.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/graphemer": { + "version": "1.4.0", + "dev": true, + "license": "MIT" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/he": { + "version": "1.2.0", + "dev": true, + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, + "node_modules/highlight.js": { + "version": "11.11.1", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.11.1.tgz", + "integrity": "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/hookable": { + "version": "5.5.3", + "license": "MIT" + }, + "node_modules/human-signals": { + "version": "8.0.1", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/is-absolute-url": { + "version": "2.1.0", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-stream": { + "version": "4.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-unicode-supported": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-what": { + "version": "4.1.16", + "license": "MIT", + "engines": { + "node": ">=12.13" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/is-wsl": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", + "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "3.1.1", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16" + } + }, + "node_modules/isomorphic-fetch": { + "version": "2.2.1", + "license": "MIT", + "dependencies": { + "node-fetch": "^1.0.1", + "whatwg-fetch": ">=0.10.0" + } + }, + "node_modules/jiti": { + "version": "2.4.2", + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/json-parse-even-better-errors": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonc-parser": { + "version": "3.3.1", + "dev": true, + "license": "MIT" + }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kolorist": { + "version": "1.8.0", + "dev": true, + "license": "MIT" + }, + "node_modules/levn": { + "version": "0.4.1", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lightningcss": { + "version": "1.30.1", + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-darwin-arm64": "1.30.1", + "lightningcss-darwin-x64": "1.30.1", + "lightningcss-freebsd-x64": "1.30.1", + "lightningcss-linux-arm-gnueabihf": "1.30.1", + "lightningcss-linux-arm64-gnu": "1.30.1", + "lightningcss-linux-arm64-musl": "1.30.1", + "lightningcss-linux-x64-gnu": "1.30.1", + "lightningcss-linux-x64-musl": "1.30.1", + "lightningcss-win32-arm64-msvc": "1.30.1", + "lightningcss-win32-x64-msvc": "1.30.1" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.30.1", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/magic-string": { + "version": "0.30.17", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, + "node_modules/memorystream": { + "version": "0.3.1", + "dev": true, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minizlib": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.2.tgz", + "integrity": "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==", + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/mitt": { + "version": "3.0.1", + "license": "MIT" + }, + "node_modules/mkdirp": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", + "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", + "license": "MIT", + "bin": { + "mkdirp": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/mrmime": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "dev": true, + "license": "MIT" + }, + "node_modules/muggle-string": { + "version": "0.4.1", + "dev": true, + "license": "MIT" + }, + "node_modules/naive-ui": { + "version": "2.42.0", + "resolved": "https://registry.npmjs.org/naive-ui/-/naive-ui-2.42.0.tgz", + "integrity": "sha512-c7cXR2YgOjgtBadXHwiWL4Y0tpGLAI5W5QzzHksOi22iuHXoSGMAzdkVTGVPE/PM0MSGQ/JtUIzCx2Y0hU0vTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@css-render/plugin-bem": "^0.15.14", + "@css-render/vue3-ssr": "^0.15.14", + "@types/katex": "^0.16.2", + "@types/lodash": "^4.14.198", + "@types/lodash-es": "^4.17.9", + "async-validator": "^4.2.5", + "css-render": "^0.15.14", + "csstype": "^3.1.3", + "date-fns": "^3.6.0", + "date-fns-tz": "^3.1.3", + "evtd": "^0.2.4", + "highlight.js": "^11.8.0", + "lodash": "^4.17.21", + "lodash-es": "^4.17.21", + "seemly": "^0.3.8", + "treemate": "^0.3.11", + "vdirs": "^0.1.8", + "vooks": "^0.2.12", + "vueuc": "^0.4.63" + }, + "peerDependencies": { + "vue": "^3.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "dev": true, + "license": "MIT" + }, + "node_modules/node-fetch": { + "version": "1.7.3", + "license": "MIT", + "dependencies": { + "encoding": "^0.1.11", + "is-stream": "^1.0.1" + } + }, + "node_modules/node-fetch/node_modules/is-stream": { + "version": "1.1.0", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/node-releases": { + "version": "2.0.19", + "dev": true, + "license": "MIT" + }, + "node_modules/npm-normalize-package-bin": { + "version": "4.0.0", + "dev": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm-run-all2": { + "version": "8.0.4", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "cross-spawn": "^7.0.6", + "memorystream": "^0.3.1", + "picomatch": "^4.0.2", + "pidtree": "^0.6.0", + "read-package-json-fast": "^4.0.0", + "shell-quote": "^1.7.3", + "which": "^5.0.0" + }, + "bin": { + "npm-run-all": "bin/npm-run-all/index.js", + "npm-run-all2": "bin/npm-run-all/index.js", + "run-p": "bin/run-p/index.js", + "run-s": "bin/run-s/index.js" + }, + "engines": { + "node": "^20.5.0 || >=22.0.0", + "npm": ">= 10" + } + }, + "node_modules/npm-run-path": { + "version": "6.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^4.0.0", + "unicorn-magic": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/open": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/open/-/open-10.2.0.tgz", + "integrity": "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "default-browser": "^5.2.1", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "wsl-utils": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/oxlint": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "bin": { + "oxc_language_server": "bin/oxc_language_server", + "oxlint": "bin/oxlint" + }, + "engines": { + "node": ">=8.*" + }, + "funding": { + "url": "https://github.com/sponsors/Boshen" + }, + "optionalDependencies": { + "@oxlint/darwin-arm64": "1.1.0", + "@oxlint/darwin-x64": "1.1.0", + "@oxlint/linux-arm64-gnu": "1.1.0", + "@oxlint/linux-arm64-musl": "1.1.0", + "@oxlint/linux-x64-gnu": "1.1.0", + "@oxlint/linux-x64-musl": "1.1.0", + "@oxlint/win32-arm64": "1.1.0", + "@oxlint/win32-x64": "1.1.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-ms": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-browserify": { + "version": "1.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/path-exists": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "dev": true, + "license": "MIT" + }, + "node_modules/perfect-debounce": { + "version": "1.0.0", + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.2", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pidtree": { + "version": "0.6.0", + "dev": true, + "license": "MIT", + "bin": { + "pidtree": "bin/pidtree.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/pinia": { + "version": "3.0.3", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^7.7.2" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "typescript": ">=4.4.4", + "vue": "^2.7.0 || ^3.5.11" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.5.3", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/pretty-ms": { + "version": "9.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "parse-ms": "^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/read-package-json-fast": { + "version": "4.0.0", + "dev": true, + "license": "ISC", + "dependencies": { + "json-parse-even-better-errors": "^4.0.0", + "npm-normalize-package-bin": "^4.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rfdc": { + "version": "1.4.1", + "license": "MIT" + }, + "node_modules/rolldown": { + "version": "1.0.0-beta.27", + "license": "MIT", + "dependencies": { + "@oxc-project/runtime": "=0.77.0", + "@oxc-project/types": "=0.77.0", + "@rolldown/pluginutils": "1.0.0-beta.27", + "ansis": "^4.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.0.0-beta.27", + "@rolldown/binding-darwin-arm64": "1.0.0-beta.27", + "@rolldown/binding-darwin-x64": "1.0.0-beta.27", + "@rolldown/binding-freebsd-x64": "1.0.0-beta.27", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-beta.27", + "@rolldown/binding-linux-arm64-gnu": "1.0.0-beta.27", + "@rolldown/binding-linux-arm64-musl": "1.0.0-beta.27", + "@rolldown/binding-linux-arm64-ohos": "1.0.0-beta.27", + "@rolldown/binding-linux-x64-gnu": "1.0.0-beta.27", + "@rolldown/binding-linux-x64-musl": "1.0.0-beta.27", + "@rolldown/binding-wasm32-wasi": "1.0.0-beta.27", + "@rolldown/binding-win32-arm64-msvc": "1.0.0-beta.27", + "@rolldown/binding-win32-ia32-msvc": "1.0.0-beta.27", + "@rolldown/binding-win32-x64-msvc": "1.0.0-beta.27" + } + }, + "node_modules/rolldown/node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "license": "MIT" + }, + "node_modules/run-applescript": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.0.0.tgz", + "integrity": "sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "license": "MIT" + }, + "node_modules/seemly": { + "version": "0.3.10", + "resolved": "https://registry.npmjs.org/seemly/-/seemly-0.3.10.tgz", + "integrity": "sha512-2+SMxtG1PcsL0uyhkumlOU6Qo9TAQ/WyH7tthnPIOQB05/12jz9naq6GZ6iZ6ApVsO3rr2gsnTf3++OV63kE1Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.7.2", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/shell-quote": { + "version": "1.8.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sirv": { + "version": "3.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@polka/url": "^1.0.0-next.24", + "mrmime": "^2.0.0", + "totalist": "^3.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/speakingurl": { + "version": "14.0.1", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-final-newline": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/superjson": { + "version": "2.2.2", + "license": "MIT", + "dependencies": { + "copy-anything": "^3.0.2" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/synckit": { + "version": "0.11.8", + "dev": true, + "license": "MIT", + "dependencies": { + "@pkgr/core": "^0.2.4" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/synckit" + } + }, + "node_modules/tailwindcss": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz", + "integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==", + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.2.tgz", + "integrity": "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/tar": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz", + "integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==", + "license": "ISC", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.0.1", + "mkdirp": "^3.0.1", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/tar/node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.14", + "license": "MIT", + "dependencies": { + "fdir": "^6.4.4", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/totalist": { + "version": "3.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/treemate": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/treemate/-/treemate-0.3.11.tgz", + "integrity": "sha512-M8RGFoKtZ8dF+iwJfAJTOH/SM4KluKOKRJpjCMhI8bG3qB74zrFoArKZ62ll0Fr3mqkMJiQOmWYkdYgDeITYQg==", + "dev": true, + "license": "MIT" + }, + "node_modules/ts-api-utils": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typescript": { + "version": "5.8.3", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.37.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.37.0", + "@typescript-eslint/parser": "8.37.0", + "@typescript-eslint/typescript-estree": "8.37.0", + "@typescript-eslint/utils": "8.37.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "devOptional": true, + "license": "MIT" + }, + "node_modules/unicorn-magic": { + "version": "0.3.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.3", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/vdirs": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/vdirs/-/vdirs-0.1.8.tgz", + "integrity": "sha512-H9V1zGRLQZg9b+GdMk8MXDN2Lva0zx72MPahDKc30v+DtwKjfyOSXWRIX4t2mhDubM1H09gPhWeth/BJWPHGUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "evtd": "^0.2.2" + }, + "peerDependencies": { + "vue": "^3.0.11" + } + }, + "node_modules/vite": { + "name": "rolldown-vite", + "version": "7.0.9", + "resolved": "https://registry.npmjs.org/rolldown-vite/-/rolldown-vite-7.0.9.tgz", + "integrity": "sha512-RxVP6CY9CNCEM9UecdytqeADxOGSjgkfSE/eI986sM7I3/F09lQ9UfQo3y6W10ICBppKsEHe71NbCX/tirYDFg==", + "license": "MIT", + "dependencies": { + "fdir": "^6.4.6", + "lightningcss": "^1.30.1", + "picomatch": "^4.0.2", + "postcss": "^8.5.6", + "rolldown": "1.0.0-beta.27", + "tinyglobby": "^0.2.14" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "esbuild": "^0.25.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite-hot-client": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vite": "^2.6.0 || ^3.0.0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0" + } + }, + "node_modules/vite-plugin-vue-devtools": { + "version": "7.7.7", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/devtools-core": "^7.7.7", + "@vue/devtools-kit": "^7.7.7", + "@vue/devtools-shared": "^7.7.7", + "execa": "^9.5.2", + "sirv": "^3.0.1", + "vite-plugin-inspect": "0.8.9", + "vite-plugin-vue-inspector": "^5.3.1" + }, + "engines": { + "node": ">=v14.21.3" + }, + "peerDependencies": { + "vite": "^3.1.0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0" + } + }, + "node_modules/vite-plugin-vue-devtools/node_modules/vite-plugin-inspect": { + "version": "0.8.9", + "resolved": "https://registry.npmjs.org/vite-plugin-inspect/-/vite-plugin-inspect-0.8.9.tgz", + "integrity": "sha512-22/8qn+LYonzibb1VeFZmISdVao5kC22jmEKm24vfFE8siEn47EpVcCLYMv6iKOYMJfjSvSJfueOwcFCkUnV3A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@antfu/utils": "^0.7.10", + "@rollup/pluginutils": "^5.1.3", + "debug": "^4.3.7", + "error-stack-parser-es": "^0.1.5", + "fs-extra": "^11.2.0", + "open": "^10.1.0", + "perfect-debounce": "^1.0.0", + "picocolors": "^1.1.1", + "sirv": "^3.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vite": "^3.1.0 || ^4.0.0 || ^5.0.0-0 || ^6.0.1" + }, + "peerDependenciesMeta": { + "@nuxt/kit": { + "optional": true + } + } + }, + "node_modules/vite-plugin-vue-inspector": { + "version": "5.3.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.23.0", + "@babel/plugin-proposal-decorators": "^7.23.0", + "@babel/plugin-syntax-import-attributes": "^7.22.5", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-transform-typescript": "^7.22.15", + "@vue/babel-plugin-jsx": "^1.1.5", + "@vue/compiler-dom": "^3.3.4", + "kolorist": "^1.8.0", + "magic-string": "^0.30.4" + }, + "peerDependencies": { + "vite": "^3.0.0-0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0" + } + }, + "node_modules/vooks": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/vooks/-/vooks-0.2.12.tgz", + "integrity": "sha512-iox0I3RZzxtKlcgYaStQYKEzWWGAduMmq+jS7OrNdQo1FgGfPMubGL3uGHOU9n97NIvfFDBGnpSvkWyb/NSn/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "evtd": "^0.2.2" + }, + "peerDependencies": { + "vue": "^3.0.0" + } + }, + "node_modules/vscode-uri": { + "version": "3.1.0", + "dev": true, + "license": "MIT" + }, + "node_modules/vue": { + "version": "3.5.17", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.17", + "@vue/compiler-sfc": "3.5.17", + "@vue/runtime-dom": "3.5.17", + "@vue/server-renderer": "3.5.17", + "@vue/shared": "3.5.17" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/vue-eslint-parser": { + "version": "10.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "eslint-scope": "^8.2.0", + "eslint-visitor-keys": "^4.2.0", + "espree": "^10.3.0", + "esquery": "^1.6.0", + "semver": "^7.6.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0" + } + }, + "node_modules/vue-router": { + "version": "4.5.1", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^6.6.4" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "vue": "^3.2.0" + } + }, + "node_modules/vue-router/node_modules/@vue/devtools-api": { + "version": "6.6.4", + "license": "MIT" + }, + "node_modules/vue-tsc": { + "version": "2.2.12", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/typescript": "2.4.15", + "@vue/language-core": "2.2.12" + }, + "bin": { + "vue-tsc": "bin/vue-tsc.js" + }, + "peerDependencies": { + "typescript": ">=5.0.0" + } + }, + "node_modules/vueuc": { + "version": "0.4.64", + "resolved": "https://registry.npmjs.org/vueuc/-/vueuc-0.4.64.tgz", + "integrity": "sha512-wlJQj7fIwKK2pOEoOq4Aro8JdPOGpX8aWQhV8YkTW9OgWD2uj2O8ANzvSsIGjx7LTOc7QbS7sXdxHi6XvRnHPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@css-render/vue3-ssr": "^0.15.10", + "@juggle/resize-observer": "^3.3.1", + "css-render": "^0.15.10", + "evtd": "^0.2.4", + "seemly": "^0.3.6", + "vdirs": "^0.1.4", + "vooks": "^0.2.4" + }, + "peerDependencies": { + "vue": "^3.0.11" + } + }, + "node_modules/whatwg-fetch": { + "version": "3.6.20", + "license": "MIT" + }, + "node_modules/which": { + "version": "5.0.0", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wsl-utils": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz", + "integrity": "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-wsl": "^3.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/xml-name-validator": { + "version": "4.0.0", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "dev": true, + "license": "ISC" + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yoctocolors": { + "version": "2.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/DysonNetwork.Pass/Client/package.json b/DysonNetwork.Pass/Client/package.json new file mode 100644 index 0000000..622bd58 --- /dev/null +++ b/DysonNetwork.Pass/Client/package.json @@ -0,0 +1,48 @@ +{ + "name": "@solar-network/pass", + "version": "0.0.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "run-p type-check \"build-only {@}\" --", + "preview": "vite preview", + "build-only": "vite build", + "type-check": "vue-tsc --build", + "lint:oxlint": "oxlint . --fix -D correctness --ignore-path .gitignore", + "lint:eslint": "eslint . --fix", + "lint": "run-s lint:*", + "format": "prettier --write src/" + }, + "dependencies": { + "@fontsource-variable/nunito": "^5.2.6", + "@tailwindcss/vite": "^4.1.11", + "aspnet-prerendering": "^3.0.1", + "pinia": "^3.0.3", + "tailwindcss": "^4.1.11", + "vue": "^3.5.17", + "vue-router": "^4.5.1" + }, + "devDependencies": { + "@tsconfig/node22": "^22.0.2", + "@types/node": "^22.16.4", + "@vicons/material": "^0.13.0", + "@vitejs/plugin-vue": "^6.0.0", + "@vitejs/plugin-vue-jsx": "^5.0.1", + "@vue/eslint-config-prettier": "^10.2.0", + "@vue/eslint-config-typescript": "^14.6.0", + "@vue/tsconfig": "^0.7.0", + "eslint": "^9.31.0", + "eslint-plugin-oxlint": "~1.1.0", + "eslint-plugin-vue": "~10.2.0", + "jiti": "^2.4.2", + "naive-ui": "^2.42.0", + "npm-run-all2": "^8.0.4", + "oxlint": "~1.1.0", + "prettier": "3.5.3", + "typescript": "~5.8.3", + "vite": "npm:rolldown-vite@latest", + "vite-plugin-vue-devtools": "^7.7.7", + "vue-tsc": "^2.2.12" + } +} \ No newline at end of file diff --git a/DysonNetwork.Pass/Client/public/favicon.ico b/DysonNetwork.Pass/Client/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..df36fcfb72584e00488330b560ebcf34a41c64c2 GIT binary patch literal 4286 zcmds*O-Phc6o&64GDVCEQHxsW(p4>LW*W<827=Unuo8sGpRux(DN@jWP-e29Wl%wj zY84_aq9}^Am9-cWTD5GGEo#+5Fi2wX_P*bo+xO!)p*7B;iKlbFd(U~_d(U?#hLj56 zPhFkj-|A6~Qk#@g^#D^U0XT1cu=c-vu1+SElX9NR;kzAUV(q0|dl0|%h|dI$%VICy zJnu2^L*Te9JrJMGh%-P79CL0}dq92RGU6gI{v2~|)p}sG5x0U*z<8U;Ij*hB9z?ei z@g6Xq-pDoPl=MANPiR7%172VA%r)kevtV-_5H*QJKFmd;8yA$98zCxBZYXTNZ#QFk2(TX0;Y2dt&WitL#$96|gJY=3xX zpCoi|YNzgO3R`f@IiEeSmKrPSf#h#Qd<$%Ej^RIeeYfsxhPMOG`S`Pz8q``=511zm zAm)MX5AV^5xIWPyEu7u>qYs?pn$I4nL9J!=K=SGlKLXpE<5x+2cDTXq?brj?n6sp= zphe9;_JHf40^9~}9i08r{XM$7HB!`{Ys~TK0kx<}ZQng`UPvH*11|q7&l9?@FQz;8 zx!=3<4seY*%=OlbCbcae?5^V_}*K>Uo6ZWV8mTyE^B=DKy7-sdLYkR5Z?paTgK-zyIkKjIcpyO z{+uIt&YSa_$QnN_@t~L014dyK(fOOo+W*MIxbA6Ndgr=Y!f#Tokqv}n<7-9qfHkc3 z=>a|HWqcX8fzQCT=dqVbogRq!-S>H%yA{1w#2Pn;=e>JiEj7Hl;zdt-2f+j2%DeVD zsW0Ab)ZK@0cIW%W7z}H{&~yGhn~D;aiP4=;m-HCo`BEI+Kd6 z={Xwx{TKxD#iCLfl2vQGDitKtN>z|-AdCN|$jTFDg0m3O`WLD4_s#$S literal 0 HcmV?d00001 diff --git a/DysonNetwork.Pass/Client/src/assets/main.css b/DysonNetwork.Pass/Client/src/assets/main.css new file mode 100644 index 0000000..49b4115 --- /dev/null +++ b/DysonNetwork.Pass/Client/src/assets/main.css @@ -0,0 +1,9 @@ +@import "tailwindcss"; + +@layer theme, base, components, utilities; + +@layer base { + body { + font-family: 'Nunito Variable', sans-serif; + } +} diff --git a/DysonNetwork.Pass/Client/src/layouts/default.vue b/DysonNetwork.Pass/Client/src/layouts/default.vue new file mode 100644 index 0000000..61f47f2 --- /dev/null +++ b/DysonNetwork.Pass/Client/src/layouts/default.vue @@ -0,0 +1,25 @@ + + + + + diff --git a/DysonNetwork.Pass/Client/src/main.ts b/DysonNetwork.Pass/Client/src/main.ts new file mode 100644 index 0000000..d535f8e --- /dev/null +++ b/DysonNetwork.Pass/Client/src/main.ts @@ -0,0 +1,16 @@ +import '@fontsource-variable/nunito'; + +import './assets/main.css' + +import { createApp } from 'vue' +import { createPinia } from 'pinia' + +import Root from './root.vue' +import router from './router' + +const app = createApp(Root) + +app.use(createPinia()) +app.use(router) + +app.mount('#app') diff --git a/DysonNetwork.Pass/Client/src/root.vue b/DysonNetwork.Pass/Client/src/root.vue new file mode 100644 index 0000000..e827a5c --- /dev/null +++ b/DysonNetwork.Pass/Client/src/root.vue @@ -0,0 +1,15 @@ + + + diff --git a/DysonNetwork.Pass/Client/src/router/index.ts b/DysonNetwork.Pass/Client/src/router/index.ts new file mode 100644 index 0000000..4e83c8c --- /dev/null +++ b/DysonNetwork.Pass/Client/src/router/index.ts @@ -0,0 +1,14 @@ +import { createRouter, createWebHistory } from 'vue-router' + +const router = createRouter({ + history: createWebHistory(import.meta.env.BASE_URL), + routes: [ + { + path: '/', + name: 'index', + component: () => import('../views/index.vue'), + }, + ], +}) + +export default router diff --git a/DysonNetwork.Pass/Client/src/views/index.vue b/DysonNetwork.Pass/Client/src/views/index.vue new file mode 100644 index 0000000..7105546 --- /dev/null +++ b/DysonNetwork.Pass/Client/src/views/index.vue @@ -0,0 +1,37 @@ + + + + + diff --git a/DysonNetwork.Pass/Client/tsconfig.app.json b/DysonNetwork.Pass/Client/tsconfig.app.json new file mode 100644 index 0000000..d0f8430 --- /dev/null +++ b/DysonNetwork.Pass/Client/tsconfig.app.json @@ -0,0 +1,12 @@ +{ + "extends": "@vue/tsconfig/tsconfig.dom.json", + "include": ["env.d.ts", "src/**/*", "src/**/*.vue", "./**/*.d.ts"], + "exclude": ["src/**/__tests__/*"], + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + + "paths": { + "@/*": ["./src/*"] + } + } +} diff --git a/DysonNetwork.Pass/Client/tsconfig.json b/DysonNetwork.Pass/Client/tsconfig.json new file mode 100644 index 0000000..66b5e57 --- /dev/null +++ b/DysonNetwork.Pass/Client/tsconfig.json @@ -0,0 +1,11 @@ +{ + "files": [], + "references": [ + { + "path": "./tsconfig.node.json" + }, + { + "path": "./tsconfig.app.json" + } + ] +} diff --git a/DysonNetwork.Pass/Client/tsconfig.node.json b/DysonNetwork.Pass/Client/tsconfig.node.json new file mode 100644 index 0000000..a83dfc9 --- /dev/null +++ b/DysonNetwork.Pass/Client/tsconfig.node.json @@ -0,0 +1,19 @@ +{ + "extends": "@tsconfig/node22/tsconfig.json", + "include": [ + "vite.config.*", + "vitest.config.*", + "cypress.config.*", + "nightwatch.conf.*", + "playwright.config.*", + "eslint.config.*" + ], + "compilerOptions": { + "noEmit": true, + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + + "module": "ESNext", + "moduleResolution": "Bundler", + "types": ["node"] + } +} diff --git a/DysonNetwork.Pass/Client/vite.config.ts b/DysonNetwork.Pass/Client/vite.config.ts new file mode 100644 index 0000000..08f1a5f --- /dev/null +++ b/DysonNetwork.Pass/Client/vite.config.ts @@ -0,0 +1,37 @@ +import { fileURLToPath, URL } from 'node:url' + +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' +import vueJsx from '@vitejs/plugin-vue-jsx' +import vueDevTools from 'vite-plugin-vue-devtools' +import tailwindcss from '@tailwindcss/vite' + +process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0' + +// https://vite.dev/config/ +export default defineConfig({ + base: '/', + plugins: [vue(), vueJsx(), vueDevTools(), tailwindcss()], + resolve: { + alias: { + '@': fileURLToPath(new URL('./src', import.meta.url)), + }, + }, + build: { + rollupOptions: { + output: { + entryFileNames: `assets/[name].js`, + chunkFileNames: `assets/[name].js`, + assetFileNames: `assets/[name].[ext]`, + }, + }, + }, + server: { + proxy: { + '/api': { + target: 'http://localhost:5216', + changeOrigin: true, + }, + }, + }, +}) diff --git a/DysonNetwork.Pass/DysonNetwork.Pass.csproj b/DysonNetwork.Pass/DysonNetwork.Pass.csproj index bf6bab7..b2d23cc 100644 --- a/DysonNetwork.Pass/DysonNetwork.Pass.csproj +++ b/DysonNetwork.Pass/DysonNetwork.Pass.csproj @@ -10,6 +10,10 @@ + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + runtime; build; native; contentfiles; analyzers; buildtransitive all @@ -44,80 +48,109 @@ - + - - True - True - NotificationResource.resx - - - True - True - SharedResource.resx - - - True - True - NotificationResource.resx - - - True - True - SharedResource.resx - - - True - True - AccountEventResource.resx - - - True - True - AccountEventResource.resx - + + True + True + NotificationResource.resx + + + True + True + SharedResource.resx + + + True + True + NotificationResource.resx + + + True + True + SharedResource.resx + + + True + True + AccountEventResource.resx + + + True + True + AccountEventResource.resx + - - ResXFileCodeGenerator - Email.LandingResource.Designer.cs - - - ResXFileCodeGenerator - NotificationResource.Designer.cs - - - ResXFileCodeGenerator - SharedResource.Designer.cs - - - ResXFileCodeGenerator - AccountEventResource.Designer.cs - + + ResXFileCodeGenerator + Email.LandingResource.Designer.cs + + + ResXFileCodeGenerator + NotificationResource.Designer.cs + + + ResXFileCodeGenerator + SharedResource.Designer.cs + + + ResXFileCodeGenerator + AccountEventResource.Designer.cs + - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <_SpaDistFiles Include="Client\dist\**\*" /> + + + + + + + + + + + <_ContentIncludedByDefault Remove="Pages\Shared\_Layout.cshtml" /> + <_ContentIncludedByDefault Remove="Pages\Checkpoint\CheckpointPage.cshtml" /> + <_ContentIncludedByDefault Remove="Pages\Spell\MagicSpellPage.cshtml" /> + + + + + diff --git a/DysonNetwork.Pass/Pages/Checkpoint/CheckpointPage.cshtml b/DysonNetwork.Pass/Pages/Checkpoint/CheckpointPage.cshtml deleted file mode 100644 index ebb8e27..0000000 --- a/DysonNetwork.Pass/Pages/Checkpoint/CheckpointPage.cshtml +++ /dev/null @@ -1,110 +0,0 @@ -@page "/auth/captcha" -@model DysonNetwork.Pass.Pages.Checkpoint.CheckpointPage - -@{ - ViewData["Title"] = "Security Checkpoint"; - var cfg = ViewData.Model.Configuration; - var provider = cfg.GetSection("Captcha")["Provider"]?.ToLower(); - var apiKey = cfg.GetSection("Captcha")["ApiKey"]; -} - -@section Scripts { - @switch (provider) - { - case "recaptcha": - - break; - case "cloudflare": - - break; - case "hcaptcha": - - break; - } - - -} - -
-
-
-
-
-

Security Check

-

Please complete the contest below to confirm you're not a robot

- -
- @switch (provider) - { - case "cloudflare": -
-
- break; - case "recaptcha": -
-
- break; - case "hcaptcha": -
-
- break; - default: -
- - Captcha provider not configured correctly. -
- break; - } -
- -
-
Solar Network Anti-Robot
-
- Powered by - @switch (provider) - { - case "cloudflare": - - Cloudflare Turnstile - - break; - case "recaptcha": - - Google reCaptcha - - break; - default: - Nothing - break; - } -
- Hosted by - - DysonNetwork.Pass - -
-
-
-
-
-
-
diff --git a/DysonNetwork.Pass/Pages/Checkpoint/CheckpointPage.cshtml.cs b/DysonNetwork.Pass/Pages/Checkpoint/CheckpointPage.cshtml.cs deleted file mode 100644 index ac561ce..0000000 --- a/DysonNetwork.Pass/Pages/Checkpoint/CheckpointPage.cshtml.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.RazorPages; - -namespace DysonNetwork.Pass.Pages.Checkpoint; - -public class CheckpointPage(IConfiguration configuration) : PageModel -{ - [BindProperty] public IConfiguration Configuration { get; set; } = configuration; - - public ActionResult OnGet() - { - return Page(); - } -} \ No newline at end of file diff --git a/DysonNetwork.Pass/Pages/Shared/_Layout.cshtml b/DysonNetwork.Pass/Pages/Shared/_Layout.cshtml deleted file mode 100644 index 7f063bf..0000000 --- a/DysonNetwork.Pass/Pages/Shared/_Layout.cshtml +++ /dev/null @@ -1,62 +0,0 @@ -@using DysonNetwork.Pass.Auth - - - - - - - @ViewData["Title"] - - - - - - - @await RenderSectionAsync("Head", required: false) - - - - -
- @RenderBody() -
- -@await RenderSectionAsync("Scripts", required: false) - - \ No newline at end of file diff --git a/DysonNetwork.Pass/Pages/Spell/MagicSpellPage.cshtml b/DysonNetwork.Pass/Pages/Spell/MagicSpellPage.cshtml deleted file mode 100644 index bd92e93..0000000 --- a/DysonNetwork.Pass/Pages/Spell/MagicSpellPage.cshtml +++ /dev/null @@ -1,91 +0,0 @@ -@page "/spells/{spellWord}" -@using DysonNetwork.Pass.Account -@model DysonNetwork.Pass.Pages.Spell.MagicSpellPage - -@{ - ViewData["Title"] = "Magic Spell"; -} - -
-
-
-

Magic Spell

- - @if (Model.IsSuccess) - { -
- - The spell was applied successfully! -

Now you can close this page.

-
- } - else if (Model.CurrentSpell == null) - { -
- - The spell was expired or does not exist. -
- } - else - { -
-
-

- @System.Text.RegularExpressions.Regex.Replace(Model.CurrentSpell!.Type.ToString(), "([a-z])([A-Z])", "$1 $2") -

-

for @@ @Model.CurrentSpell.Account?.Name

-
- @if (Model.CurrentSpell.ExpiresAt.HasValue) - { -

Available until @Model.CurrentSpell.ExpiresAt.Value.ToDateTimeUtc().ToString("g")

- } - @if (Model.CurrentSpell.AffectedAt.HasValue) - { -

Available after @Model.CurrentSpell.AffectedAt.Value.ToDateTimeUtc().ToString("g")

- } -
-

Would you like to apply this spell?

- -
- - - @if (Model.CurrentSpell?.Type == MagicSpellType.AuthPasswordReset) - { -
- - -
- } - -
- -
-
-
-
- } - -
-
Solar Network
-
- - Solsynth LLC - - © @DateTime.Now.Year -
- Powered by - - DysonNetwork.Pass - -
-
-
-
-
diff --git a/DysonNetwork.Pass/Pages/Spell/MagicSpellPage.cshtml.cs b/DysonNetwork.Pass/Pages/Spell/MagicSpellPage.cshtml.cs deleted file mode 100644 index 14d57a9..0000000 --- a/DysonNetwork.Pass/Pages/Spell/MagicSpellPage.cshtml.cs +++ /dev/null @@ -1,52 +0,0 @@ -using DysonNetwork.Pass.Account; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.RazorPages; -using Microsoft.EntityFrameworkCore; -using NodaTime; - -namespace DysonNetwork.Pass.Pages.Spell; - -public class MagicSpellPage(AppDatabase db, MagicSpellService spells) : PageModel -{ - [BindProperty] public MagicSpell? CurrentSpell { get; set; } - [BindProperty] public string? NewPassword { get; set; } - - public bool IsSuccess { get; set; } - - public async Task OnGetAsync(string spellWord) - { - spellWord = Uri.UnescapeDataString(spellWord); - var now = SystemClock.Instance.GetCurrentInstant(); - CurrentSpell = await db.MagicSpells - .Where(e => e.Spell == spellWord) - .Where(e => e.ExpiresAt == null || now < e.ExpiresAt) - .Where(e => e.AffectedAt == null || now >= e.AffectedAt) - .Include(e => e.Account) - .FirstOrDefaultAsync(); - - return Page(); - } - - public async Task OnPostAsync() - { - if (CurrentSpell?.Id == null) - return Page(); - - var now = SystemClock.Instance.GetCurrentInstant(); - var spell = await db.MagicSpells - .Where(e => e.Id == CurrentSpell.Id) - .Where(e => e.ExpiresAt == null || now < e.ExpiresAt) - .Where(e => e.AffectedAt == null || now >= e.AffectedAt) - .FirstOrDefaultAsync(); - - if (spell == null || spell.Type == MagicSpellType.AuthPasswordReset && string.IsNullOrWhiteSpace(NewPassword)) - return Page(); - - if (spell.Type == MagicSpellType.AuthPasswordReset) - await spells.ApplyPasswordReset(spell, NewPassword!); - else - await spells.ApplyMagicSpell(spell); - IsSuccess = true; - return Page(); - } -} \ No newline at end of file diff --git a/DysonNetwork.Pass/Pages/_ViewImports.cshtml b/DysonNetwork.Pass/Pages/_ViewImports.cshtml deleted file mode 100644 index 4b4ce1d..0000000 --- a/DysonNetwork.Pass/Pages/_ViewImports.cshtml +++ /dev/null @@ -1,2 +0,0 @@ -@namespace DysonNetwork.Pass.Pages -@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers \ No newline at end of file diff --git a/DysonNetwork.Pass/Pages/_ViewStart.cshtml b/DysonNetwork.Pass/Pages/_ViewStart.cshtml deleted file mode 100644 index 40c70bc..0000000 --- a/DysonNetwork.Pass/Pages/_ViewStart.cshtml +++ /dev/null @@ -1,3 +0,0 @@ -@{ - Layout = "_Layout"; -} \ No newline at end of file diff --git a/DysonNetwork.Pass/Program.cs b/DysonNetwork.Pass/Program.cs index e1cec59..9dd6e86 100644 --- a/DysonNetwork.Pass/Program.cs +++ b/DysonNetwork.Pass/Program.cs @@ -3,6 +3,7 @@ using DysonNetwork.Pass.Startup; using DysonNetwork.Shared.Http; using DysonNetwork.Shared.Registry; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.FileProviders; var builder = WebApplication.CreateBuilder(args); @@ -39,7 +40,7 @@ using (var scope = app.Services.CreateScope()) } // Configure application middleware pipeline -app.ConfigureAppMiddleware(builder.Configuration); +app.ConfigureAppMiddleware(builder.Configuration, builder.Environment.ContentRootPath); // Configure gRPC app.ConfigureGrpcServices(); diff --git a/DysonNetwork.Pass/Startup/ApplicationConfiguration.cs b/DysonNetwork.Pass/Startup/ApplicationConfiguration.cs index df51b15..f7498da 100644 --- a/DysonNetwork.Pass/Startup/ApplicationConfiguration.cs +++ b/DysonNetwork.Pass/Startup/ApplicationConfiguration.cs @@ -3,20 +3,21 @@ using DysonNetwork.Pass.Account; using DysonNetwork.Pass.Auth; using DysonNetwork.Pass.Permission; using Microsoft.AspNetCore.HttpOverrides; +using Microsoft.Extensions.FileProviders; using Prometheus; namespace DysonNetwork.Pass.Startup; public static class ApplicationConfiguration { - public static WebApplication ConfigureAppMiddleware(this WebApplication app, IConfiguration configuration) + public static WebApplication ConfigureAppMiddleware(this WebApplication app, IConfiguration configuration, string contentRoot) { app.MapMetrics(); app.MapOpenApi(); app.UseSwagger(); app.UseSwaggerUI(); - + app.UseRequestLocalization(); ConfigureForwardedHeaders(app, configuration); @@ -37,9 +38,15 @@ public static class ApplicationConfiguration app.UseAuthorization(); app.UseMiddleware(); + app.UseDefaultFiles(); + app.UseStaticFiles(new StaticFileOptions + { + FileProvider = new PhysicalFileProvider(Path.Combine(contentRoot, "wwwroot", "dist")) + }); + app.MapControllers().RequireRateLimiting("fixed"); - app.MapStaticAssets().RequireRateLimiting("fixed"); - app.MapRazorPages().RequireRateLimiting("fixed"); + + app.MapFallbackToFile("dist/index.html"); return app; } @@ -71,7 +78,7 @@ public static class ApplicationConfiguration app.MapGrpcService(); app.MapGrpcService(); app.MapGrpcService(); - + return app; } -} +} \ No newline at end of file diff --git a/DysonNetwork.Pass/VersionController.cs b/DysonNetwork.Pass/VersionController.cs new file mode 100644 index 0000000..3beaed5 --- /dev/null +++ b/DysonNetwork.Pass/VersionController.cs @@ -0,0 +1,20 @@ +using Microsoft.AspNetCore.Mvc; +using DysonNetwork.Shared.Data; + +namespace DysonNetwork.Pass; + +[ApiController] +[Route("/api/version")] +public class VersionController : ControllerBase +{ + [HttpGet] + public IActionResult Get() + { + return Ok(new AppVersion + { + Version = ThisAssembly.AssemblyVersion, + Commit = ThisAssembly.GitCommitId, + UpdateDate = ThisAssembly.GitCommitDate + }); + } +} diff --git a/DysonNetwork.Pass/version.json b/DysonNetwork.Pass/version.json new file mode 100644 index 0000000..9fbf8d3 --- /dev/null +++ b/DysonNetwork.Pass/version.json @@ -0,0 +1,7 @@ +{ + "version": "1.0", + "publicReleaseRefSpec": ["^refs/heads/main$"], + "cloudBuild": { + "setVersionVariables": true + } +} diff --git a/DysonNetwork.Pusher/DysonNetwork.Pusher.csproj b/DysonNetwork.Pusher/DysonNetwork.Pusher.csproj index a9aedab..4e1b9d7 100644 --- a/DysonNetwork.Pusher/DysonNetwork.Pusher.csproj +++ b/DysonNetwork.Pusher/DysonNetwork.Pusher.csproj @@ -18,6 +18,10 @@ runtime; build; native; contentfiles; analyzers; buildtransitive all
+ + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/DysonNetwork.Shared/Data/AppVersion.cs b/DysonNetwork.Shared/Data/AppVersion.cs new file mode 100644 index 0000000..1accf0a --- /dev/null +++ b/DysonNetwork.Shared/Data/AppVersion.cs @@ -0,0 +1,8 @@ +namespace DysonNetwork.Shared.Data; + +public record class AppVersion +{ + public required string Version { get; init; } + public required string Commit { get; init; } + public required DateTimeOffset UpdateDate { get; init; } +} diff --git a/DysonNetwork.Sphere/DysonNetwork.Sphere.csproj b/DysonNetwork.Sphere/DysonNetwork.Sphere.csproj index 3ddb288..4e3511a 100644 --- a/DysonNetwork.Sphere/DysonNetwork.Sphere.csproj +++ b/DysonNetwork.Sphere/DysonNetwork.Sphere.csproj @@ -41,6 +41,10 @@ runtime; build; native; contentfiles; analyzers; buildtransitive + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/DysonNetwork.Sphere/Pages/Shared/_ValidationScriptsPartial.cshtml b/DysonNetwork.Sphere/Pages/Shared/_ValidationScriptsPartial.cshtml deleted file mode 100644 index e36029c..0000000 --- a/DysonNetwork.Sphere/Pages/Shared/_ValidationScriptsPartial.cshtml +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/DysonNetwork.Sphere/Pages/_ViewImports.cshtml b/DysonNetwork.Sphere/Pages/_ViewImports.cshtml index 313f074..f861a21 100644 --- a/DysonNetwork.Sphere/Pages/_ViewImports.cshtml +++ b/DysonNetwork.Sphere/Pages/_ViewImports.cshtml @@ -1,3 +1,4 @@ @using DysonNetwork.Sphere @namespace DysonNetwork.Sphere.Pages -@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers \ No newline at end of file +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers +@addTagHelper *, Microsoft.AspNetCore.SpaServices \ No newline at end of file diff --git a/DysonNetwork.Sphere/VersionController.cs b/DysonNetwork.Sphere/VersionController.cs new file mode 100644 index 0000000..c406ab2 --- /dev/null +++ b/DysonNetwork.Sphere/VersionController.cs @@ -0,0 +1,20 @@ +using DysonNetwork.Shared.Data; +using Microsoft.AspNetCore.Mvc; + +namespace DysonNetwork.Sphere; + +[ApiController] +[Route("/api/version")] +public class VersionController : ControllerBase +{ + [HttpGet] + public IActionResult Get() + { + return Ok(new AppVersion + { + Version = ThisAssembly.AssemblyVersion, + Commit = ThisAssembly.GitCommitId, + UpdateDate = ThisAssembly.GitCommitDate + }); + } +} diff --git a/DysonNetwork.Sphere/version.json b/DysonNetwork.Sphere/version.json new file mode 100644 index 0000000..9fbf8d3 --- /dev/null +++ b/DysonNetwork.Sphere/version.json @@ -0,0 +1,7 @@ +{ + "version": "1.0", + "publicReleaseRefSpec": ["^refs/heads/main$"], + "cloudBuild": { + "setVersionVariables": true + } +} diff --git a/DysonNetwork.sln.DotSettings.user b/DysonNetwork.sln.DotSettings.user index 04f33c9..d207423 100644 --- a/DysonNetwork.sln.DotSettings.user +++ b/DysonNetwork.sln.DotSettings.user @@ -97,6 +97,7 @@ ForceIncluded ForceIncluded ForceIncluded + ForceIncluded ForceIncluded ForceIncluded ForceIncluded From e4dcf2517acdcce336353c56d1d3b8705224e57c Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Wed, 16 Jul 2025 13:00:10 +0800 Subject: [PATCH 25/42] :bricks: Mixed page infra --- DysonNetwork.Pass/Client/index.html | 13 +++--- .../Client/src/views/authorize.vue | 0 DysonNetwork.Pass/Client/src/views/index.vue | 9 ++-- DysonNetwork.Pass/Client/src/views/login.vue | 0 .../Pages/Data/VersionPageData.cs | 24 +++++++++++ DysonNetwork.Pass/Program.cs | 7 ++++ .../Startup/ApplicationConfiguration.cs | 2 - .../PageData/IPageDataProvider.cs | 9 ++++ DysonNetwork.Shared/PageData/Startup.cs | 42 +++++++++++++++++++ DysonNetwork.sln.DotSettings.user | 1 + 10 files changed, 95 insertions(+), 12 deletions(-) create mode 100644 DysonNetwork.Pass/Client/src/views/authorize.vue create mode 100644 DysonNetwork.Pass/Client/src/views/login.vue create mode 100644 DysonNetwork.Pass/Pages/Data/VersionPageData.cs create mode 100644 DysonNetwork.Shared/PageData/IPageDataProvider.cs create mode 100644 DysonNetwork.Shared/PageData/Startup.cs diff --git a/DysonNetwork.Pass/Client/index.html b/DysonNetwork.Pass/Client/index.html index a5ca9ee..08ed20b 100644 --- a/DysonNetwork.Pass/Client/index.html +++ b/DysonNetwork.Pass/Client/index.html @@ -1,13 +1,14 @@ - + Solarpass - - -
- - + %%APP_DATA%% + + +
+ + diff --git a/DysonNetwork.Pass/Client/src/views/authorize.vue b/DysonNetwork.Pass/Client/src/views/authorize.vue new file mode 100644 index 0000000..e69de29 diff --git a/DysonNetwork.Pass/Client/src/views/index.vue b/DysonNetwork.Pass/Client/src/views/index.vue index 7105546..c401745 100644 --- a/DysonNetwork.Pass/Client/src/views/index.vue +++ b/DysonNetwork.Pass/Client/src/views/index.vue @@ -9,10 +9,11 @@

Loading... - v{{ version.version }} @ {{ version.commit.substring(0, 6) }} - {{ version.updatedAt }} + + v{{ version.version }} @ + {{ version.commit.substring(0, 6) }} + {{ version.updatedAt }} +

diff --git a/DysonNetwork.Pass/Client/src/views/login.vue b/DysonNetwork.Pass/Client/src/views/login.vue new file mode 100644 index 0000000..e69de29 diff --git a/DysonNetwork.Pass/Pages/Data/VersionPageData.cs b/DysonNetwork.Pass/Pages/Data/VersionPageData.cs new file mode 100644 index 0000000..dcc8600 --- /dev/null +++ b/DysonNetwork.Pass/Pages/Data/VersionPageData.cs @@ -0,0 +1,24 @@ +using DysonNetwork.Shared.Data; +using DysonNetwork.Shared.PageData; + +namespace DysonNetwork.Pass.Pages.Data; + +public class VersionPageData : IPageDataProvider +{ + public bool CanHandlePath(PathString path) => true; + + public Task> GetAppDataAsync(HttpContext context) + { + var versionData = new AppVersion + { + Version = ThisAssembly.AssemblyVersion, + Commit = ThisAssembly.GitCommitId, + UpdateDate = ThisAssembly.GitCommitDate + }; + + var result = typeof(AppVersion).GetProperties() + .ToDictionary(property => property.Name, property => property.GetValue(versionData)); + + return Task.FromResult>(result); + } +} \ No newline at end of file diff --git a/DysonNetwork.Pass/Program.cs b/DysonNetwork.Pass/Program.cs index 9dd6e86..e571f69 100644 --- a/DysonNetwork.Pass/Program.cs +++ b/DysonNetwork.Pass/Program.cs @@ -1,6 +1,9 @@ +using System.Text.Json; using DysonNetwork.Pass; +using DysonNetwork.Pass.Pages.Data; using DysonNetwork.Pass.Startup; using DysonNetwork.Shared.Http; +using DysonNetwork.Shared.PageData; using DysonNetwork.Shared.Registry; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.FileProviders; @@ -30,6 +33,8 @@ builder.Services.AddAppBusinessServices(builder.Configuration); // Add scheduled jobs builder.Services.AddAppScheduledJobs(); +builder.Services.AddTransient(); + var app = builder.Build(); // Run database migrations @@ -42,6 +47,8 @@ using (var scope = app.Services.CreateScope()) // Configure application middleware pipeline app.ConfigureAppMiddleware(builder.Configuration, builder.Environment.ContentRootPath); +app.MapPages(Path.Combine(builder.Environment.WebRootPath, "dist", "index.html")); + // Configure gRPC app.ConfigureGrpcServices(); diff --git a/DysonNetwork.Pass/Startup/ApplicationConfiguration.cs b/DysonNetwork.Pass/Startup/ApplicationConfiguration.cs index f7498da..da69c11 100644 --- a/DysonNetwork.Pass/Startup/ApplicationConfiguration.cs +++ b/DysonNetwork.Pass/Startup/ApplicationConfiguration.cs @@ -46,8 +46,6 @@ public static class ApplicationConfiguration app.MapControllers().RequireRateLimiting("fixed"); - app.MapFallbackToFile("dist/index.html"); - return app; } diff --git a/DysonNetwork.Shared/PageData/IPageDataProvider.cs b/DysonNetwork.Shared/PageData/IPageDataProvider.cs new file mode 100644 index 0000000..aaacf89 --- /dev/null +++ b/DysonNetwork.Shared/PageData/IPageDataProvider.cs @@ -0,0 +1,9 @@ +using Microsoft.AspNetCore.Http; + +namespace DysonNetwork.Shared.PageData; + +public interface IPageDataProvider +{ + bool CanHandlePath(PathString path); + Task> GetAppDataAsync(HttpContext context); +} \ No newline at end of file diff --git a/DysonNetwork.Shared/PageData/Startup.cs b/DysonNetwork.Shared/PageData/Startup.cs new file mode 100644 index 0000000..8769d95 --- /dev/null +++ b/DysonNetwork.Shared/PageData/Startup.cs @@ -0,0 +1,42 @@ +using System.Text.Json; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.DependencyInjection; + +namespace DysonNetwork.Shared.PageData; + +public static class PageStartup +{ + public static WebApplication MapPages(this WebApplication app, string defaultFile) + { +#pragma warning disable ASP0016 + app.MapFallback(async context => + { + var html = await File.ReadAllTextAsync(defaultFile); + + using var scope = app.Services.CreateScope(); + var providers = scope.ServiceProvider.GetServices(); + + var matches = providers + .Where(p => p.CanHandlePath(context.Request.Path)) + .Select(p => p.GetAppDataAsync(context)) + .ToList(); + var results = await Task.WhenAll(matches); + + var appData = new Dictionary(); + foreach (var result in results) + foreach (var (key, value) in result) + appData[key] = value; + + var json = JsonSerializer.Serialize(appData); + html = html.Replace("%%APP_DATA%%", $""); + + context.Response.ContentType = "text/html"; + await context.Response.WriteAsync(html); + }); +#pragma warning restore ASP0016 + + return app; + } +} \ No newline at end of file diff --git a/DysonNetwork.sln.DotSettings.user b/DysonNetwork.sln.DotSettings.user index d207423..5967b91 100644 --- a/DysonNetwork.sln.DotSettings.user +++ b/DysonNetwork.sln.DotSettings.user @@ -43,6 +43,7 @@ ForceIncluded ForceIncluded ForceIncluded + ForceIncluded ForceIncluded ForceIncluded ForceIncluded From 022f89c36e17afa5c3d2a6d759675f58d46a3c4b Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Wed, 16 Jul 2025 22:29:46 +0800 Subject: [PATCH 26/42] :sparkles: Pass service provide recaptcha --- .../Startup/ApplicationBuilderExtensions.cs | 1 - DysonNetwork.Pass/Client/public/favicon.ico | Bin 4286 -> 0 bytes DysonNetwork.Pass/Client/src/router/index.ts | 5 + .../Client/src/views/captcha.vue | 98 ++++++++++++++++++ DysonNetwork.Pass/DysonNetwork.Pass.csproj | 1 + .../Pages/Data/CaptchaPageData.cs | 20 ++++ DysonNetwork.Pass/Program.cs | 1 + .../Startup/ApplicationConfiguration.cs | 1 - .../Startup/ApplicationConfiguration.cs | 1 - DysonNetwork.Shared/Proto/GrpcClientHelper.cs | 4 +- .../Startup/ApplicationConfiguration.cs | 1 - 11 files changed, 127 insertions(+), 6 deletions(-) delete mode 100644 DysonNetwork.Pass/Client/public/favicon.ico create mode 100644 DysonNetwork.Pass/Client/src/views/captcha.vue create mode 100644 DysonNetwork.Pass/Pages/Data/CaptchaPageData.cs diff --git a/DysonNetwork.Drive/Startup/ApplicationBuilderExtensions.cs b/DysonNetwork.Drive/Startup/ApplicationBuilderExtensions.cs index 698dabe..ef2896b 100644 --- a/DysonNetwork.Drive/Startup/ApplicationBuilderExtensions.cs +++ b/DysonNetwork.Drive/Startup/ApplicationBuilderExtensions.cs @@ -15,7 +15,6 @@ public static class ApplicationBuilderExtensions app.UseSwaggerUI(); } - app.UseHttpsRedirection(); app.UseAuthorization(); app.MapControllers(); diff --git a/DysonNetwork.Pass/Client/public/favicon.ico b/DysonNetwork.Pass/Client/public/favicon.ico deleted file mode 100644 index df36fcfb72584e00488330b560ebcf34a41c64c2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4286 zcmds*O-Phc6o&64GDVCEQHxsW(p4>LW*W<827=Unuo8sGpRux(DN@jWP-e29Wl%wj zY84_aq9}^Am9-cWTD5GGEo#+5Fi2wX_P*bo+xO!)p*7B;iKlbFd(U~_d(U?#hLj56 zPhFkj-|A6~Qk#@g^#D^U0XT1cu=c-vu1+SElX9NR;kzAUV(q0|dl0|%h|dI$%VICy zJnu2^L*Te9JrJMGh%-P79CL0}dq92RGU6gI{v2~|)p}sG5x0U*z<8U;Ij*hB9z?ei z@g6Xq-pDoPl=MANPiR7%172VA%r)kevtV-_5H*QJKFmd;8yA$98zCxBZYXTNZ#QFk2(TX0;Y2dt&WitL#$96|gJY=3xX zpCoi|YNzgO3R`f@IiEeSmKrPSf#h#Qd<$%Ej^RIeeYfsxhPMOG`S`Pz8q``=511zm zAm)MX5AV^5xIWPyEu7u>qYs?pn$I4nL9J!=K=SGlKLXpE<5x+2cDTXq?brj?n6sp= zphe9;_JHf40^9~}9i08r{XM$7HB!`{Ys~TK0kx<}ZQng`UPvH*11|q7&l9?@FQz;8 zx!=3<4seY*%=OlbCbcae?5^V_}*K>Uo6ZWV8mTyE^B=DKy7-sdLYkR5Z?paTgK-zyIkKjIcpyO z{+uIt&YSa_$QnN_@t~L014dyK(fOOo+W*MIxbA6Ndgr=Y!f#Tokqv}n<7-9qfHkc3 z=>a|HWqcX8fzQCT=dqVbogRq!-S>H%yA{1w#2Pn;=e>JiEj7Hl;zdt-2f+j2%DeVD zsW0Ab)ZK@0cIW%W7z}H{&~yGhn~D;aiP4=;m-HCo`BEI+Kd6 z={Xwx{TKxD#iCLfl2vQGDitKtN>z|-AdCN|$jTFDg0m3O`WLD4_s#$S diff --git a/DysonNetwork.Pass/Client/src/router/index.ts b/DysonNetwork.Pass/Client/src/router/index.ts index 4e83c8c..413ea9b 100644 --- a/DysonNetwork.Pass/Client/src/router/index.ts +++ b/DysonNetwork.Pass/Client/src/router/index.ts @@ -8,6 +8,11 @@ const router = createRouter({ name: 'index', component: () => import('../views/index.vue'), }, + { + path: '/captcha', + name: 'captcha', + component: () => import('../views/captcha.vue'), + } ], }) diff --git a/DysonNetwork.Pass/Client/src/views/captcha.vue b/DysonNetwork.Pass/Client/src/views/captcha.vue new file mode 100644 index 0000000..f30d490 --- /dev/null +++ b/DysonNetwork.Pass/Client/src/views/captcha.vue @@ -0,0 +1,98 @@ + + + diff --git a/DysonNetwork.Pass/DysonNetwork.Pass.csproj b/DysonNetwork.Pass/DysonNetwork.Pass.csproj index b2d23cc..872b9d3 100644 --- a/DysonNetwork.Pass/DysonNetwork.Pass.csproj +++ b/DysonNetwork.Pass/DysonNetwork.Pass.csproj @@ -149,6 +149,7 @@ + diff --git a/DysonNetwork.Pass/Pages/Data/CaptchaPageData.cs b/DysonNetwork.Pass/Pages/Data/CaptchaPageData.cs new file mode 100644 index 0000000..d9a382f --- /dev/null +++ b/DysonNetwork.Pass/Pages/Data/CaptchaPageData.cs @@ -0,0 +1,20 @@ +using DysonNetwork.Shared.PageData; + +namespace DysonNetwork.Pass.Pages.Data; + +public class CaptchaPageData(IConfiguration configuration) : IPageDataProvider +{ + public bool CanHandlePath(PathString path) => path == "/captcha"; + + public Task> GetAppDataAsync(HttpContext context) + { + var provider = configuration.GetSection("Captcha")["Provider"]?.ToLower(); + var apiKey = configuration.GetSection("Captcha")["ApiKey"]; + + return Task.FromResult>(new Dictionary + { + ["Provider"] = provider, + ["ApiKey"] = apiKey + }); + } +} \ No newline at end of file diff --git a/DysonNetwork.Pass/Program.cs b/DysonNetwork.Pass/Program.cs index e571f69..2b32e21 100644 --- a/DysonNetwork.Pass/Program.cs +++ b/DysonNetwork.Pass/Program.cs @@ -34,6 +34,7 @@ builder.Services.AddAppBusinessServices(builder.Configuration); builder.Services.AddAppScheduledJobs(); builder.Services.AddTransient(); +builder.Services.AddTransient(); var app = builder.Build(); diff --git a/DysonNetwork.Pass/Startup/ApplicationConfiguration.cs b/DysonNetwork.Pass/Startup/ApplicationConfiguration.cs index da69c11..d9fa82e 100644 --- a/DysonNetwork.Pass/Startup/ApplicationConfiguration.cs +++ b/DysonNetwork.Pass/Startup/ApplicationConfiguration.cs @@ -33,7 +33,6 @@ public static class ApplicationConfiguration app.UseWebSockets(); app.UseRateLimiter(); - app.UseHttpsRedirection(); app.UseAuthentication(); app.UseAuthorization(); app.UseMiddleware(); diff --git a/DysonNetwork.Pusher/Startup/ApplicationConfiguration.cs b/DysonNetwork.Pusher/Startup/ApplicationConfiguration.cs index eacf9fa..7dc3f30 100644 --- a/DysonNetwork.Pusher/Startup/ApplicationConfiguration.cs +++ b/DysonNetwork.Pusher/Startup/ApplicationConfiguration.cs @@ -28,7 +28,6 @@ public static class ApplicationConfiguration app.UseWebSockets(); app.UseRateLimiter(); - app.UseHttpsRedirection(); app.UseAuthentication(); app.UseAuthorization(); diff --git a/DysonNetwork.Shared/Proto/GrpcClientHelper.cs b/DysonNetwork.Shared/Proto/GrpcClientHelper.cs index 05b3213..172fe05 100644 --- a/DysonNetwork.Shared/Proto/GrpcClientHelper.cs +++ b/DysonNetwork.Shared/Proto/GrpcClientHelper.cs @@ -93,7 +93,7 @@ public static class GrpcClientHelper string? clientCertPassword = null ) { - var url = await GetServiceUrlFromEtcd(etcdClient, "DysonNetwork.File"); + var url = await GetServiceUrlFromEtcd(etcdClient, "DysonNetwork.Drive"); return new FileService.FileServiceClient(CreateCallInvoker(url, clientCertPath, clientKeyPath, clientCertPassword)); } @@ -105,7 +105,7 @@ public static class GrpcClientHelper string? clientCertPassword = null ) { - var url = await GetServiceUrlFromEtcd(etcdClient, "DysonNetwork.FileReference"); + var url = await GetServiceUrlFromEtcd(etcdClient, "DysonNetwork.Drive"); return new FileReferenceService.FileReferenceServiceClient(CreateCallInvoker(url, clientCertPath, clientKeyPath, clientCertPassword)); } diff --git a/DysonNetwork.Sphere/Startup/ApplicationConfiguration.cs b/DysonNetwork.Sphere/Startup/ApplicationConfiguration.cs index ee62b91..32a5e2b 100644 --- a/DysonNetwork.Sphere/Startup/ApplicationConfiguration.cs +++ b/DysonNetwork.Sphere/Startup/ApplicationConfiguration.cs @@ -31,7 +31,6 @@ public static class ApplicationConfiguration app.UseWebSockets(); app.UseRateLimiter(); - app.UseHttpsRedirection(); app.UseAuthentication(); app.UseAuthorization(); app.UseMiddleware(); From b14af43996dc3881933771ca732451f689722cd4 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Wed, 16 Jul 2025 23:43:43 +0800 Subject: [PATCH 27/42] :bug: Fixes captcha --- .../Controllers/WellKnownController.cs | 33 +++++++++++++++++- DysonNetwork.Gateway/appsettings.json | 1 + .../Client/src/views/captcha.vue | 34 +++++++++++++++---- 3 files changed, 60 insertions(+), 8 deletions(-) diff --git a/DysonNetwork.Gateway/Controllers/WellKnownController.cs b/DysonNetwork.Gateway/Controllers/WellKnownController.cs index 811ec68..d5129fb 100644 --- a/DysonNetwork.Gateway/Controllers/WellKnownController.cs +++ b/DysonNetwork.Gateway/Controllers/WellKnownController.cs @@ -1,3 +1,5 @@ +using System.Text; +using dotnet_etcd.interfaces; using Microsoft.AspNetCore.Mvc; using Yarp.ReverseProxy.Configuration; @@ -5,7 +7,10 @@ namespace DysonNetwork.Gateway.Controllers; [ApiController] [Route("/.well-known")] -public class WellKnownController(IConfiguration configuration, IProxyConfigProvider proxyConfigProvider) +public class WellKnownController( + IConfiguration configuration, + IProxyConfigProvider proxyConfigProvider, + IEtcdClient etcdClient) : ControllerBase { [HttpGet("domains")] @@ -16,6 +21,32 @@ public class WellKnownController(IConfiguration configuration, IProxyConfigProvi return Ok(domainMappings); } + [HttpGet("services")] + public IActionResult GetServices() + { + var local = configuration.GetValue("LocalMode"); + var response = etcdClient.GetRange("/services/"); + var kvs = response.Kvs; + + var serviceMap = kvs.ToDictionary( + kv => Encoding.UTF8.GetString(kv.Key.ToByteArray()).Replace("/services/", ""), + kv => Encoding.UTF8.GetString(kv.Value.ToByteArray()) + ); + + if (local) return Ok(serviceMap); + + var domainMappings = configuration.GetSection("DomainMappings").GetChildren() + .ToDictionary(x => x.Key, x => x.Value); + foreach (var (key, _) in serviceMap.ToList()) + { + if (!domainMappings.TryGetValue(key, out var domain)) continue; + if (domain is not null) + serviceMap[key] = domain; + } + + return Ok(serviceMap); + } + [HttpGet("routes")] public IActionResult GetProxyRules() { diff --git a/DysonNetwork.Gateway/appsettings.json b/DysonNetwork.Gateway/appsettings.json index 6d0486f..858b693 100644 --- a/DysonNetwork.Gateway/appsettings.json +++ b/DysonNetwork.Gateway/appsettings.json @@ -1,4 +1,5 @@ { + "LocalMode": true, "Logging": { "LogLevel": { "Default": "Information", diff --git a/DysonNetwork.Pass/Client/src/views/captcha.vue b/DysonNetwork.Pass/Client/src/views/captcha.vue index f30d490..7d7a815 100644 --- a/DysonNetwork.Pass/Client/src/views/captcha.vue +++ b/DysonNetwork.Pass/Client/src/views/captcha.vue @@ -1,10 +1,10 @@