Magic spell for one time code

🗑️ Drop the usage of casbin
♻️ Refactor the permission service
♻️ Refactor the flow of creating an account
🧱 Email infra structure
This commit is contained in:
LittleSheep 2025-04-29 20:37:10 +08:00
parent 82288fa52c
commit 0ebeab672b
35 changed files with 1789 additions and 5214 deletions

View File

@ -13,6 +13,7 @@ public class Account : ModelBase
[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!;
@ -24,8 +25,6 @@ public class Account : ModelBase
[JsonIgnore] public ICollection<Relationship> OutgoingRelationships { get; set; } = new List<Relationship>();
[JsonIgnore] public ICollection<Relationship> IncomingRelationships { get; set; } = new List<Relationship>();
[JsonIgnore] public ICollection<PermissionGroupMember> GroupMemberships { get; set; } = new List<PermissionGroupMember>();
}
public class Profile : ModelBase

View File

@ -1,15 +1,23 @@
using System.ComponentModel.DataAnnotations;
using DysonNetwork.Sphere.Auth;
using DysonNetwork.Sphere.Storage;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory;
using NodaTime;
namespace DysonNetwork.Sphere.Account;
[ApiController]
[Route("/accounts")]
public class AccountController(AppDatabase db, FileService fs, IMemoryCache memCache) : ControllerBase
public class AccountController(
AppDatabase db,
FileService fs,
AuthService auth,
MagicSpellService spells,
IMemoryCache memCache
) : ControllerBase
{
[HttpGet("{name}")]
[ProducesResponseType<Account>(StatusCodes.Status200OK)]
@ -39,6 +47,10 @@ public class AccountController(AppDatabase db, FileService fs, IMemoryCache memC
[MinLength(4)]
[MaxLength(128)]
public string Password { get; set; } = string.Empty;
[MaxLength(128)] public string Language { get; set; } = "en";
[Required] public string CaptchaToken { get; set; } = string.Empty;
}
[HttpPost]
@ -46,6 +58,8 @@ public class AccountController(AppDatabase db, FileService fs, IMemoryCache memC
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<ActionResult<Account>> CreateAccount([FromBody] AccountCreateRequest request)
{
if (!await auth.ValidateCaptcha(request.CaptchaToken)) return BadRequest("Invalid captcha token.");
var dupeNameCount = await db.Accounts.Where(a => a.Name == request.Name).CountAsync();
if (dupeNameCount > 0)
return BadRequest("The name is already taken.");
@ -54,6 +68,7 @@ public class AccountController(AppDatabase db, FileService fs, IMemoryCache memC
{
Name = request.Name,
Nick = request.Nick,
Language = request.Language,
Contacts = new List<AccountContact>
{
new()
@ -75,6 +90,18 @@ public class AccountController(AppDatabase db, FileService fs, IMemoryCache memC
await db.Accounts.AddAsync(account);
await db.SaveChangesAsync();
var spell = await spells.CreateMagicSpell(
account,
MagicSpellType.AccountActivation,
new Dictionary<string, object>
{
{ "contact_method", account.Contacts.First().Content }
},
expiredAt: SystemClock.Instance.GetCurrentInstant().Plus(Duration.FromDays(7))
);
spells.NotifyMagicSpell(spell);
return account;
}
@ -110,7 +137,7 @@ public class AccountController(AppDatabase db, FileService fs, IMemoryCache memC
if (request.Nick is not null) account.Nick = request.Nick;
if (request.Language is not null) account.Language = request.Language;
memCache.Remove($"user_${account.Id}");
await db.SaveChangesAsync();
@ -171,9 +198,9 @@ public class AccountController(AppDatabase db, FileService fs, IMemoryCache memC
db.Update(profile);
await db.SaveChangesAsync();
memCache.Remove($"user_${userId}");
return profile;
}
}

View File

@ -1,10 +1,11 @@
using Casbin;
using DysonNetwork.Sphere.Permission;
using Microsoft.EntityFrameworkCore;
using NodaTime;
namespace DysonNetwork.Sphere.Account;
public class AccountService(AppDatabase db, IEnforcer enforcer)
public class AccountService(AppDatabase db, PermissionService pm)
{
public async Task<Account?> LookupAccount(string probe)
{
@ -145,18 +146,18 @@ public class AccountService(AppDatabase db, IEnforcer enforcer)
// others: use the default permissions by design
var domain = $"user:{relationship.AccountId.ToString()}";
var target = relationship.RelatedId.ToString();
var target = $"user:{relationship.RelatedId.ToString()}";
await enforcer.DeleteRolesForUserAsync(target, domain);
await pm.RemovePermissionNode(target, domain, "*");
string role = relationship.Status switch
bool? value = relationship.Status switch
{
RelationshipStatus.Friends => "friends",
RelationshipStatus.Blocked => "blocked",
_ => "default" // fallback role
RelationshipStatus.Friends => true,
RelationshipStatus.Blocked => false,
_ => null,
};
if (role == "default") return;
if (value is null) return;
await enforcer.AddRoleForUserAsync(target, role, domain);
await pm.AddPermissionNode(target, domain, "*", value);
}
}

View File

@ -0,0 +1,49 @@
using MailKit.Net.Smtp;
using MimeKit;
namespace DysonNetwork.Sphere.Account;
public class EmailServiceConfiguration
{
public string Server { get; set; }
public int Port { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public string FromAddress { get; set; }
public string FromName { get; set; }
public string SubjectPrefix { get; set; }
}
public class EmailService
{
private readonly EmailServiceConfiguration _configuration;
public EmailService(IConfiguration configuration)
{
var cfg = configuration.GetValue<EmailServiceConfiguration>("Email");
_configuration = cfg ?? throw new ArgumentException("Email service was not configured.");
}
public async Task SendEmailAsync(string? recipientName, string recipientEmail, string subject, string textBody)
{
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
};
emailMessage.Body = bodyBuilder.ToMessageBody();
using var client = new SmtpClient();
await client.ConnectAsync(_configuration.Server, _configuration.Port, true);
await client.AuthenticateAsync(_configuration.Username, _configuration.Password);
await client.SendAsync(emailMessage);
await client.DisconnectAsync(true);
}
}

View File

@ -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.Sphere.Account;
public enum MagicSpellType
{
AccountActivation,
AccountDeactivation,
AccountRemoval,
AuthFactorReset,
ContactVerification,
}
[Index(nameof(Spell), IsUnique = true)]
public class MagicSpell : ModelBase
{
public Guid Id { get; set; } = Guid.NewGuid();
[JsonIgnore] [MaxLength(1024)] public string Spell { get; set; } = null!;
public MagicSpellType Type { get; set; }
public Instant? ExpiresAt { get; set; }
public Instant? AffectedAt { get; set; }
[Column(TypeName = "jsonb")] public Dictionary<string, object> Meta { get; set; }
public long? AccountId { get; set; }
public Account? Account { get; set; }
}

View File

@ -0,0 +1,122 @@
using System.Security.Cryptography;
using DysonNetwork.Sphere.Permission;
using Microsoft.EntityFrameworkCore;
using NodaTime;
namespace DysonNetwork.Sphere.Account;
public class MagicSpellService(AppDatabase db, EmailService email, ILogger<MagicSpellService> logger)
{
public async Task<MagicSpell> CreateMagicSpell(
Account account,
MagicSpellType type,
Dictionary<string, object> meta,
Instant? expiredAt = null,
Instant? affectedAt = null
)
{
var spellWord = _GenerateRandomString(128);
var spell = new MagicSpell
{
Spell = spellWord,
Type = type,
ExpiresAt = expiredAt,
AffectedAt = affectedAt,
Account = account,
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)
.Include(c => c.Account)
.FirstOrDefaultAsync();
if (contact is null) throw new ArgumentException("Account has no contact method that can use");
// TODO replace the baseurl
var link = $"https://api.sn.solsynth.dev/spells/{spell}";
try
{
switch (spell.Type)
{
case MagicSpellType.AccountActivation:
await email.SendEmailAsync(
contact.Account.Name,
contact.Content,
"Confirm your registration",
"Thank you for creating an account.\n" +
"For accessing all the features, confirm your registration with the link below:\n\n" +
$"{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.AccountActivation:
var contact = await
db.AccountContacts.FirstOrDefaultAsync(c =>
c.Account.Id == spell.AccountId && c.Content == spell.Meta["contact_method"] as string
);
if (contact is not null)
{
contact.VerifiedAt = SystemClock.Instance.GetCurrentInstant();
db.Update(contact);
}
var 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
});
}
await db.SaveChangesAsync();
break;
default:
throw new ArgumentOutOfRangeException();
}
}
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);
}
}

View File

@ -1,4 +1,5 @@
using System.Linq.Expressions;
using DysonNetwork.Sphere.Permission;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
using NodaTime;
@ -19,18 +20,24 @@ public class AppDatabase(
IConfiguration configuration
) : DbContext(options)
{
public DbSet<Permission.PermissionNode> PermissionNodes { get; set; } = null!;
public DbSet<Permission.PermissionGroup> PermissionGroups { get; set; } = null!;
public DbSet<PermissionNode> PermissionNodes { get; set; } = null!;
public DbSet<PermissionGroup> PermissionGroups { get; set; } = null!;
public DbSet<PermissionGroupMember> PermissionGroupMembers { get; set; } = null!;
public DbSet<Account.MagicSpell> MagicSpells { get; set; } = null!;
public DbSet<Account.Account> Accounts { get; set; }
public DbSet<Account.Profile> AccountProfiles { get; set; }
public DbSet<Account.AccountContact> AccountContacts { get; set; }
public DbSet<Account.AccountAuthFactor> AccountAuthFactors { get; set; }
public DbSet<Account.Relationship> AccountRelationships { get; set; }
public DbSet<Auth.Session> AuthSessions { get; set; }
public DbSet<Auth.Challenge> AuthChallenges { get; set; }
public DbSet<Account.Notification> Notifications { get; set; }
public DbSet<Account.NotificationPushSubscription> NotificationPushSubscriptions { get; set; }
public DbSet<Auth.Session> AuthSessions { get; set; }
public DbSet<Auth.Challenge> AuthChallenges { get; set; }
public DbSet<Storage.CloudFile> Files { get; set; }
public DbSet<Post.Publisher> Publishers { get; set; }
public DbSet<Post.PublisherMember> PublisherMembers { get; set; }
public DbSet<Post.Post> Posts { get; set; }
@ -51,6 +58,25 @@ public class AppDatabase(
opt => opt.UseNodaTime()
).UseSnakeCaseNamingConvention();
optionsBuilder.UseAsyncSeeding(async (context, _, cancellationToken) =>
{
var defaultPermissionGroup = await context.Set<PermissionGroup>()
.FirstOrDefaultAsync(g => g.Key == "default", cancellationToken);
if (defaultPermissionGroup is null)
{
context.Set<PermissionGroup>().Add(new PermissionGroup
{
Key = "default",
Nodes =
{
PermissionService.NewPermissionNode("group:default", "global", "posts.create", true),
PermissionService.NewPermissionNode("group:default", "global", "publishers.create", true)
}
});
await context.SaveChangesAsync(cancellationToken);
}
});
base.OnConfiguring(optionsBuilder);
}
@ -59,19 +85,13 @@ public class AppDatabase(
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Permission.PermissionGroupMember>()
.HasKey(pg => new { pg.GroupId, pg.AccountId })
.HasName("permission_group_members");
.HasKey(pg => new { pg.GroupId, pg.Actor });
modelBuilder.Entity<Permission.PermissionGroupMember>()
.HasOne(pg => pg.Group)
.WithMany(g => g.Members)
.HasForeignKey(pg => pg.GroupId)
.OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<Permission.PermissionGroupMember>()
.HasOne(pg => pg.Account)
.WithMany(a => a.GroupMemberships)
.HasForeignKey(pg => pg.AccountId)
.OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<Account.Account>()
.HasOne(a => a.Profile)
.WithOne(p => p.Account)
@ -87,7 +107,7 @@ public class AppDatabase(
.HasOne(r => r.Related)
.WithMany(a => a.IncomingRelationships)
.HasForeignKey(r => r.RelatedId);
modelBuilder.Entity<Post.PublisherMember>()
.HasKey(pm => new { pm.PublisherId, pm.AccountId });
modelBuilder.Entity<Post.PublisherMember>()
@ -100,7 +120,7 @@ public class AppDatabase(
.WithMany()
.HasForeignKey(pm => pm.AccountId)
.OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<Post.Post>()
.HasOne(p => p.ThreadedPost)
.WithOne()
@ -113,7 +133,7 @@ public class AppDatabase(
modelBuilder.Entity<Post.Post>()
.HasOne(p => p.ForwardedPost)
.WithMany()
.HasForeignKey(p => p.ForwardedPostId)
.HasForeignKey(p => p.ForwardedPostId)
.OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<Post.Post>()
.HasMany(p => p.Tags)

View File

@ -15,8 +15,7 @@ public class AuthController(
AppDatabase db,
AccountService accounts,
AuthService auth,
IConfiguration configuration,
IHttpClientFactory httpClientFactory
IConfiguration configuration
) : ControllerBase
{
public class ChallengeRequest
@ -218,49 +217,7 @@ public class AuthController(
[HttpPost("captcha")]
public async Task<ActionResult> ValidateCaptcha([FromBody] string token)
{
var provider = configuration.GetSection("Captcha")["Provider"]?.ToLower();
var apiKey = configuration.GetSection("Captcha")["ApiKey"];
var apiSecret = configuration.GetSection("Captcha")["ApiSecret"];
var client = httpClientFactory.CreateClient();
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 cfResult = JsonSerializer.Deserialize<CloudflareVerificationResponse>(json);
if (cfResult?.Success == true)
return Ok(new { success = true });
return BadRequest(new { success = false, errors = cfResult?.ErrorCodes });
case "google":
var secretKey = configuration.GetSection("CaptchaSettings")["GoogleRecaptchaSecretKey"];
if (string.IsNullOrEmpty(secretKey))
{
return StatusCode(500, "Google reCaptcha secret key is not configured.");
}
content = new StringContent($"secret={secretKey}&response={token}", System.Text.Encoding.UTF8,
"application/x-www-form-urlencoded");
response = await client.PostAsync("https://www.google.com/recaptcha/api/siteverify", content);
response.EnsureSuccessStatusCode();
json = await response.Content.ReadAsStringAsync();
var capResult = JsonSerializer.Deserialize<GoogleVerificationResponse>(json);
if (capResult?.Success == true)
return Ok(new { success = true });
return BadRequest(new { success = false, errors = capResult?.ErrorCodes });
default:
return StatusCode(500, "The server misconfigured for the captcha.");
}
var result = await auth.ValidateCaptcha(token);
return result ? Ok() : BadRequest();
}
}

View File

@ -1,6 +1,7 @@
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text.Json;
using Casbin;
using Microsoft.AspNetCore.Authorization;
using Microsoft.IdentityModel.Tokens;
@ -15,68 +16,43 @@ public class SignedTokenPair
public Instant ExpiredAt { get; set; }
}
public class AuthService(AppDatabase db, IConfiguration config, IEnforcer enforcer)
public class AuthService(IConfiguration config, IHttpClientFactory httpClientFactory)
{
public async Task<bool> AssignRoleToUserAsync(string user, string role, string domain = "global")
public async Task<bool> ValidateCaptcha(string token)
{
var added = await enforcer.AddGroupingPolicyAsync(user, role, domain);
if (added) await enforcer.SavePolicyAsync();
return added;
}
var provider = config.GetSection("Captcha")["Provider"]?.ToLower();
var apiSecret = config.GetSection("Captcha")["ApiSecret"];
public async Task<bool> AddPermissionToUserAsync(string user, string domain, string obj, string act)
{
var added = await enforcer.AddPolicyAsync(user, domain, obj, act);
if (added) await enforcer.SavePolicyAsync();
return added;
}
public async Task<bool> RemovePermissionFromUserAsync(string user, string domain, string obj, string act)
{
var removed = await enforcer.RemovePolicyAsync(user, domain, obj, act);
if (removed) await enforcer.SavePolicyAsync();
return removed;
}
var client = httpClientFactory.CreateClient();
public async Task<bool> CreateRoleAsync(string role, string domain, IEnumerable<(string obj, string act)> permissions)
{
bool anyAdded = false;
foreach (var (obj, act) in permissions)
switch (provider)
{
var added = await enforcer.AddPolicyAsync(role, domain, obj, act);
if (added) anyAdded = true;
}
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();
if (anyAdded) await enforcer.SavePolicyAsync();
return anyAdded;
var json = await response.Content.ReadAsStringAsync();
var cfResult = JsonSerializer.Deserialize<CloudflareVerificationResponse>(json);
return cfResult?.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/api/siteverify", content);
response.EnsureSuccessStatusCode();
json = await response.Content.ReadAsStringAsync();
var capResult = JsonSerializer.Deserialize<GoogleVerificationResponse>(json);
return capResult?.Success == true;
default:
throw new ArgumentException("The server misconfigured for the captcha.");
}
}
public async Task<bool> AddPermissionsToRoleAsync(string role, string domain, IEnumerable<(string obj, string act)> permissions)
{
bool anyAdded = false;
foreach (var (obj, act) in permissions)
{
var added = await enforcer.AddPolicyAsync(role, domain, obj, act);
if (added) anyAdded = true;
}
if (anyAdded) await enforcer.SavePolicyAsync();
return anyAdded;
}
public async Task<bool> RemovePermissionsFromRoleAsync(string role, string domain, IEnumerable<(string obj, string act)> permissions)
{
bool anyRemoved = false;
foreach (var (obj, act) in permissions)
{
var removed = await enforcer.RemovePolicyAsync(role, domain, obj, act);
if (removed) anyRemoved = true;
}
if (anyRemoved) await enforcer.SavePolicyAsync();
return anyRemoved;
}
public SignedTokenPair CreateToken(Session session)
{
var privateKeyPem = File.ReadAllText(config["Jwt:PrivateKeyPath"]!);
@ -98,9 +74,9 @@ public class AuthService(AppDatabase db, IConfiguration config, IEnforcer enforc
expires: DateTime.Now.AddDays(30),
signingCredentials: creds
);
session.Challenge.Scopes.ForEach(c => claims.Add(new Claim("scope", c)));
if(session.Account.IsSuperuser) claims.Add(new Claim("is_superuser", "1"));
if (session.Account.IsSuperuser) claims.Add(new Claim("is_superuser", "1"));
var accessTokenClaims = new JwtSecurityToken(
issuer: "solar-network",
audience: string.Join(',', session.Challenge.Audiences),

View File

@ -1,34 +0,0 @@
using Casbin;
using Microsoft.AspNetCore.Authorization;
namespace DysonNetwork.Sphere.Auth;
public class CasbinRequirement(string domain, string obj, string act) : IAuthorizationRequirement
{
public string Domain { get; } = domain;
public string Object { get; } = obj;
public string Action { get; } = act;
}
public class CasbinAuthorizationHandler(IEnforcer enforcer)
: AuthorizationHandler<CasbinRequirement>
{
protected override async Task HandleRequirementAsync(
AuthorizationHandlerContext context,
CasbinRequirement requirement)
{
var userId = context.User.FindFirst("user_id")?.Value;
if (userId == null) return;
var isSuperuser = context.User.FindFirst("is_superuser")?.Value == "1";
if (isSuperuser) userId = "super:" + userId;
var allowed = await enforcer.EnforceAsync(
userId,
requirement.Domain,
requirement.Object,
requirement.Action
);
if (allowed) context.Succeed(requirement);
}
}

View File

@ -21,6 +21,7 @@ public class Challenge : ModelBase
public Instant? ExpiredAt { get; set; }
public int StepRemain { get; set; }
public int StepTotal { get; set; }
public int FailedAttempts { get; set; }
[Column(TypeName = "jsonb")] public List<long> BlacklistFactors { get; set; } = new();
[Column(TypeName = "jsonb")] public List<string> Audiences { get; set; } = new();
[Column(TypeName = "jsonb")] public List<string> Scopes { get; set; } = new();

View File

@ -16,6 +16,7 @@
<PackageReference Include="CorePush" Version="4.3.0" />
<PackageReference Include="EFCore.NamingConventions" Version="9.0.0" />
<PackageReference Include="FFMpegCore" Version="5.2.0" />
<PackageReference Include="MailKit" Version="4.11.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.4" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.3">

View File

@ -1,541 +0,0 @@
// <auto-generated />
using System;
using System.Collections.Generic;
using DysonNetwork.Sphere;
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.Sphere.Migrations
{
[DbContext(typeof(AppDatabase))]
[Migration("20250413115936_InitialMigration")]
partial class InitialMigration
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "9.0.3")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("DysonNetwork.Sphere.Account.Account", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasColumnName("id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
b.Property<Instant>("CreatedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("created_at");
b.Property<Instant?>("DeletedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("deleted_at");
b.Property<bool>("IsSuperuser")
.HasColumnType("boolean")
.HasColumnName("is_superuser");
b.Property<string>("Language")
.IsRequired()
.HasMaxLength(32)
.HasColumnType("character varying(32)")
.HasColumnName("language");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(256)
.HasColumnType("character varying(256)")
.HasColumnName("name");
b.Property<string>("Nick")
.IsRequired()
.HasMaxLength(256)
.HasColumnType("character varying(256)")
.HasColumnName("nick");
b.Property<Instant>("UpdatedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("updated_at");
b.HasKey("Id")
.HasName("pk_accounts");
b.ToTable("accounts", (string)null);
});
modelBuilder.Entity("DysonNetwork.Sphere.Account.AccountAuthFactor", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasColumnName("id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
b.Property<long>("AccountId")
.HasColumnType("bigint")
.HasColumnName("account_id");
b.Property<Instant>("CreatedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("created_at");
b.Property<Instant?>("DeletedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("deleted_at");
b.Property<string>("Secret")
.HasColumnType("text")
.HasColumnName("secret");
b.Property<int>("Type")
.HasColumnType("integer")
.HasColumnName("type");
b.Property<Instant>("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<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasColumnName("id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
b.Property<long>("AccountId")
.HasColumnType("bigint")
.HasColumnName("account_id");
b.Property<string>("Content")
.IsRequired()
.HasMaxLength(1024)
.HasColumnType("character varying(1024)")
.HasColumnName("content");
b.Property<Instant>("CreatedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("created_at");
b.Property<Instant?>("DeletedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("deleted_at");
b.Property<int>("Type")
.HasColumnType("integer")
.HasColumnName("type");
b.Property<Instant>("UpdatedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("updated_at");
b.Property<Instant?>("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.Profile", b =>
{
b.Property<long>("Id")
.HasColumnType("bigint")
.HasColumnName("id");
b.Property<string>("BackgroundId")
.HasColumnType("text")
.HasColumnName("background_id");
b.Property<string>("Bio")
.HasMaxLength(4096)
.HasColumnType("character varying(4096)")
.HasColumnName("bio");
b.Property<Instant>("CreatedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("created_at");
b.Property<Instant?>("DeletedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("deleted_at");
b.Property<string>("FirstName")
.HasMaxLength(256)
.HasColumnType("character varying(256)")
.HasColumnName("first_name");
b.Property<string>("LastName")
.HasMaxLength(256)
.HasColumnType("character varying(256)")
.HasColumnName("last_name");
b.Property<string>("MiddleName")
.HasMaxLength(256)
.HasColumnType("character varying(256)")
.HasColumnName("middle_name");
b.Property<string>("PictureId")
.HasColumnType("text")
.HasColumnName("picture_id");
b.Property<Instant>("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.Auth.Challenge", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid")
.HasColumnName("id");
b.Property<long>("AccountId")
.HasColumnType("bigint")
.HasColumnName("account_id");
b.Property<List<string>>("Audiences")
.IsRequired()
.HasColumnType("jsonb")
.HasColumnName("audiences");
b.Property<List<long>>("BlacklistFactors")
.IsRequired()
.HasColumnType("jsonb")
.HasColumnName("blacklist_factors");
b.Property<Instant>("CreatedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("created_at");
b.Property<Instant?>("DeletedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("deleted_at");
b.Property<string>("DeviceId")
.HasMaxLength(256)
.HasColumnType("character varying(256)")
.HasColumnName("device_id");
b.Property<Instant?>("ExpiredAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("expired_at");
b.Property<string>("IpAddress")
.HasMaxLength(128)
.HasColumnType("character varying(128)")
.HasColumnName("ip_address");
b.Property<string>("Nonce")
.HasMaxLength(1024)
.HasColumnType("character varying(1024)")
.HasColumnName("nonce");
b.Property<List<string>>("Scopes")
.IsRequired()
.HasColumnType("jsonb")
.HasColumnName("scopes");
b.Property<int>("StepRemain")
.HasColumnType("integer")
.HasColumnName("step_remain");
b.Property<int>("StepTotal")
.HasColumnType("integer")
.HasColumnName("step_total");
b.Property<Instant>("UpdatedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("updated_at");
b.Property<string>("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<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid")
.HasColumnName("id");
b.Property<long>("AccountId")
.HasColumnType("bigint")
.HasColumnName("account_id");
b.Property<Guid>("ChallengeId")
.HasColumnType("uuid")
.HasColumnName("challenge_id");
b.Property<Instant>("CreatedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("created_at");
b.Property<Instant?>("DeletedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("deleted_at");
b.Property<Instant?>("ExpiredAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("expired_at");
b.Property<Instant?>("LastGrantedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("last_granted_at");
b.Property<Instant>("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.Storage.CloudFile", b =>
{
b.Property<string>("Id")
.HasColumnType("text")
.HasColumnName("id");
b.Property<long>("AccountId")
.HasColumnType("bigint")
.HasColumnName("account_id");
b.Property<Instant>("CreatedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("created_at");
b.Property<Instant?>("DeletedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("deleted_at");
b.Property<string>("Description")
.HasMaxLength(4096)
.HasColumnType("character varying(4096)")
.HasColumnName("description");
b.Property<Dictionary<string, object>>("FileMeta")
.HasColumnType("jsonb")
.HasColumnName("file_meta");
b.Property<string>("Hash")
.HasMaxLength(256)
.HasColumnType("character varying(256)")
.HasColumnName("hash");
b.Property<string>("MimeType")
.HasMaxLength(256)
.HasColumnType("character varying(256)")
.HasColumnName("mime_type");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(1024)
.HasColumnType("character varying(1024)")
.HasColumnName("name");
b.Property<long>("Size")
.HasColumnType("bigint")
.HasColumnName("size");
b.Property<Instant>("UpdatedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("updated_at");
b.Property<Instant?>("UploadedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("uploaded_at");
b.Property<string>("UploadedTo")
.HasMaxLength(128)
.HasColumnType("character varying(128)")
.HasColumnName("uploaded_to");
b.Property<int>("UsedCount")
.HasColumnType("integer")
.HasColumnName("used_count");
b.Property<Dictionary<string, object>>("UserMeta")
.HasColumnType("jsonb")
.HasColumnName("user_meta");
b.HasKey("Id")
.HasName("pk_files");
b.HasIndex("AccountId")
.HasDatabaseName("ix_files_account_id");
b.ToTable("files", (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.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.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.Storage.CloudFile", b =>
{
b.HasOne("DysonNetwork.Sphere.Account.Account", "Account")
.WithMany()
.HasForeignKey("AccountId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("fk_files_accounts_account_id");
b.Navigation("Account");
});
modelBuilder.Entity("DysonNetwork.Sphere.Account.Account", b =>
{
b.Navigation("AuthFactors");
b.Navigation("Challenges");
b.Navigation("Contacts");
b.Navigation("Profile")
.IsRequired();
b.Navigation("Sessions");
});
#pragma warning restore 612, 618
}
}
}

View File

@ -1,279 +0,0 @@
using System;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore.Migrations;
using NodaTime;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace DysonNetwork.Sphere.Migrations
{
/// <inheritdoc />
public partial class InitialMigration : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "accounts",
columns: table => new
{
id = table.Column<long>(type: "bigint", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
name = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
nick = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
language = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: false),
is_superuser = table.Column<bool>(type: "boolean", nullable: false),
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("pk_accounts", x => x.id);
});
migrationBuilder.CreateTable(
name: "account_auth_factors",
columns: table => new
{
id = table.Column<long>(type: "bigint", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
type = table.Column<int>(type: "integer", nullable: false),
secret = table.Column<string>(type: "text", nullable: true),
account_id = table.Column<long>(type: "bigint", nullable: false),
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("pk_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_contacts",
columns: table => new
{
id = table.Column<long>(type: "bigint", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
type = table.Column<int>(type: "integer", nullable: false),
verified_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
content = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false),
account_id = table.Column<long>(type: "bigint", nullable: false),
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("pk_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: "auth_challenges",
columns: table => new
{
id = table.Column<Guid>(type: "uuid", nullable: false),
expired_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
step_remain = table.Column<int>(type: "integer", nullable: false),
step_total = table.Column<int>(type: "integer", nullable: false),
blacklist_factors = table.Column<List<long>>(type: "jsonb", nullable: false),
audiences = table.Column<List<string>>(type: "jsonb", nullable: false),
scopes = table.Column<List<string>>(type: "jsonb", nullable: false),
ip_address = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: true),
user_agent = table.Column<string>(type: "character varying(512)", maxLength: 512, nullable: true),
device_id = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
nonce = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: true),
account_id = table.Column<long>(type: "bigint", nullable: false),
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("pk_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: "files",
columns: table => new
{
id = table.Column<string>(type: "text", nullable: false),
name = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false),
description = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: true),
file_meta = table.Column<Dictionary<string, object>>(type: "jsonb", nullable: true),
user_meta = table.Column<Dictionary<string, object>>(type: "jsonb", nullable: true),
mime_type = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
hash = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
size = table.Column<long>(type: "bigint", nullable: false),
uploaded_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
uploaded_to = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: true),
used_count = table.Column<int>(type: "integer", nullable: false),
account_id = table.Column<long>(type: "bigint", nullable: false),
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("pk_files", x => x.id);
table.ForeignKey(
name: "fk_files_accounts_account_id",
column: x => x.account_id,
principalTable: "accounts",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "auth_sessions",
columns: table => new
{
id = table.Column<Guid>(type: "uuid", nullable: false),
last_granted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
expired_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
account_id = table.Column<long>(type: "bigint", nullable: false),
challenge_id = table.Column<Guid>(type: "uuid", nullable: false),
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("pk_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: "account_profiles",
columns: table => new
{
id = table.Column<long>(type: "bigint", nullable: false),
first_name = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
middle_name = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
last_name = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
bio = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: true),
picture_id = table.Column<string>(type: "text", nullable: true),
background_id = table.Column<string>(type: "text", nullable: true),
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true)
},
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);
table.ForeignKey(
name: "fk_account_profiles_files_background_id",
column: x => x.background_id,
principalTable: "files",
principalColumn: "id");
table.ForeignKey(
name: "fk_account_profiles_files_picture_id",
column: x => x.picture_id,
principalTable: "files",
principalColumn: "id");
});
migrationBuilder.CreateIndex(
name: "ix_account_auth_factors_account_id",
table: "account_auth_factors",
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_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_files_account_id",
table: "files",
column: "account_id");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "account_auth_factors");
migrationBuilder.DropTable(
name: "account_contacts");
migrationBuilder.DropTable(
name: "account_profiles");
migrationBuilder.DropTable(
name: "auth_sessions");
migrationBuilder.DropTable(
name: "files");
migrationBuilder.DropTable(
name: "auth_challenges");
migrationBuilder.DropTable(
name: "accounts");
}
}
}

View File

@ -1,605 +0,0 @@
// <auto-generated />
using System;
using System.Collections.Generic;
using DysonNetwork.Sphere;
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.Sphere.Migrations
{
[DbContext(typeof(AppDatabase))]
[Migration("20250417145426_AddRelationship")]
partial class AddRelationship
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "9.0.3")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("DysonNetwork.Sphere.Account.Account", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasColumnName("id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
b.Property<Instant>("CreatedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("created_at");
b.Property<Instant?>("DeletedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("deleted_at");
b.Property<bool>("IsSuperuser")
.HasColumnType("boolean")
.HasColumnName("is_superuser");
b.Property<string>("Language")
.IsRequired()
.HasMaxLength(32)
.HasColumnType("character varying(32)")
.HasColumnName("language");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(256)
.HasColumnType("character varying(256)")
.HasColumnName("name");
b.Property<string>("Nick")
.IsRequired()
.HasMaxLength(256)
.HasColumnType("character varying(256)")
.HasColumnName("nick");
b.Property<Instant>("UpdatedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("updated_at");
b.HasKey("Id")
.HasName("pk_accounts");
b.ToTable("accounts", (string)null);
});
modelBuilder.Entity("DysonNetwork.Sphere.Account.AccountAuthFactor", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasColumnName("id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
b.Property<long>("AccountId")
.HasColumnType("bigint")
.HasColumnName("account_id");
b.Property<Instant>("CreatedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("created_at");
b.Property<Instant?>("DeletedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("deleted_at");
b.Property<string>("Secret")
.HasColumnType("text")
.HasColumnName("secret");
b.Property<int>("Type")
.HasColumnType("integer")
.HasColumnName("type");
b.Property<Instant>("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<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasColumnName("id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
b.Property<long>("AccountId")
.HasColumnType("bigint")
.HasColumnName("account_id");
b.Property<string>("Content")
.IsRequired()
.HasMaxLength(1024)
.HasColumnType("character varying(1024)")
.HasColumnName("content");
b.Property<Instant>("CreatedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("created_at");
b.Property<Instant?>("DeletedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("deleted_at");
b.Property<int>("Type")
.HasColumnType("integer")
.HasColumnName("type");
b.Property<Instant>("UpdatedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("updated_at");
b.Property<Instant?>("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.Profile", b =>
{
b.Property<long>("Id")
.HasColumnType("bigint")
.HasColumnName("id");
b.Property<string>("BackgroundId")
.HasColumnType("text")
.HasColumnName("background_id");
b.Property<string>("Bio")
.HasMaxLength(4096)
.HasColumnType("character varying(4096)")
.HasColumnName("bio");
b.Property<Instant>("CreatedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("created_at");
b.Property<Instant?>("DeletedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("deleted_at");
b.Property<string>("FirstName")
.HasMaxLength(256)
.HasColumnType("character varying(256)")
.HasColumnName("first_name");
b.Property<string>("LastName")
.HasMaxLength(256)
.HasColumnType("character varying(256)")
.HasColumnName("last_name");
b.Property<string>("MiddleName")
.HasMaxLength(256)
.HasColumnType("character varying(256)")
.HasColumnName("middle_name");
b.Property<string>("PictureId")
.HasColumnType("text")
.HasColumnName("picture_id");
b.Property<Instant>("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<long>("AccountId")
.HasColumnType("bigint")
.HasColumnName("account_id");
b.Property<long>("RelatedId")
.HasColumnType("bigint")
.HasColumnName("related_id");
b.Property<Instant>("CreatedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("created_at");
b.Property<Instant?>("DeletedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("deleted_at");
b.Property<Instant?>("ExpiredAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("expired_at");
b.Property<int>("Status")
.HasColumnType("integer")
.HasColumnName("status");
b.Property<Instant>("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.Auth.Challenge", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid")
.HasColumnName("id");
b.Property<long>("AccountId")
.HasColumnType("bigint")
.HasColumnName("account_id");
b.Property<List<string>>("Audiences")
.IsRequired()
.HasColumnType("jsonb")
.HasColumnName("audiences");
b.Property<List<long>>("BlacklistFactors")
.IsRequired()
.HasColumnType("jsonb")
.HasColumnName("blacklist_factors");
b.Property<Instant>("CreatedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("created_at");
b.Property<Instant?>("DeletedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("deleted_at");
b.Property<string>("DeviceId")
.HasMaxLength(256)
.HasColumnType("character varying(256)")
.HasColumnName("device_id");
b.Property<Instant?>("ExpiredAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("expired_at");
b.Property<string>("IpAddress")
.HasMaxLength(128)
.HasColumnType("character varying(128)")
.HasColumnName("ip_address");
b.Property<string>("Nonce")
.HasMaxLength(1024)
.HasColumnType("character varying(1024)")
.HasColumnName("nonce");
b.Property<List<string>>("Scopes")
.IsRequired()
.HasColumnType("jsonb")
.HasColumnName("scopes");
b.Property<int>("StepRemain")
.HasColumnType("integer")
.HasColumnName("step_remain");
b.Property<int>("StepTotal")
.HasColumnType("integer")
.HasColumnName("step_total");
b.Property<Instant>("UpdatedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("updated_at");
b.Property<string>("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<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid")
.HasColumnName("id");
b.Property<long>("AccountId")
.HasColumnType("bigint")
.HasColumnName("account_id");
b.Property<Guid>("ChallengeId")
.HasColumnType("uuid")
.HasColumnName("challenge_id");
b.Property<Instant>("CreatedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("created_at");
b.Property<Instant?>("DeletedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("deleted_at");
b.Property<Instant?>("ExpiredAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("expired_at");
b.Property<Instant?>("LastGrantedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("last_granted_at");
b.Property<Instant>("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.Storage.CloudFile", b =>
{
b.Property<string>("Id")
.HasColumnType("text")
.HasColumnName("id");
b.Property<long>("AccountId")
.HasColumnType("bigint")
.HasColumnName("account_id");
b.Property<Instant>("CreatedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("created_at");
b.Property<Instant?>("DeletedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("deleted_at");
b.Property<string>("Description")
.HasMaxLength(4096)
.HasColumnType("character varying(4096)")
.HasColumnName("description");
b.Property<Dictionary<string, object>>("FileMeta")
.HasColumnType("jsonb")
.HasColumnName("file_meta");
b.Property<string>("Hash")
.HasMaxLength(256)
.HasColumnType("character varying(256)")
.HasColumnName("hash");
b.Property<string>("MimeType")
.HasMaxLength(256)
.HasColumnType("character varying(256)")
.HasColumnName("mime_type");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(1024)
.HasColumnType("character varying(1024)")
.HasColumnName("name");
b.Property<long>("Size")
.HasColumnType("bigint")
.HasColumnName("size");
b.Property<Instant>("UpdatedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("updated_at");
b.Property<Instant?>("UploadedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("uploaded_at");
b.Property<string>("UploadedTo")
.HasMaxLength(128)
.HasColumnType("character varying(128)")
.HasColumnName("uploaded_to");
b.Property<int>("UsedCount")
.HasColumnType("integer")
.HasColumnName("used_count");
b.Property<Dictionary<string, object>>("UserMeta")
.HasColumnType("jsonb")
.HasColumnName("user_meta");
b.HasKey("Id")
.HasName("pk_files");
b.HasIndex("AccountId")
.HasDatabaseName("ix_files_account_id");
b.ToTable("files", (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.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.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.Storage.CloudFile", b =>
{
b.HasOne("DysonNetwork.Sphere.Account.Account", "Account")
.WithMany()
.HasForeignKey("AccountId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("fk_files_accounts_account_id");
b.Navigation("Account");
});
modelBuilder.Entity("DysonNetwork.Sphere.Account.Account", b =>
{
b.Navigation("AuthFactors");
b.Navigation("Challenges");
b.Navigation("Contacts");
b.Navigation("IncomingRelationships");
b.Navigation("OutgoingRelationships");
b.Navigation("Profile")
.IsRequired();
b.Navigation("Sessions");
});
#pragma warning restore 612, 618
}
}
}

View File

@ -1,56 +0,0 @@
using Microsoft.EntityFrameworkCore.Migrations;
using NodaTime;
#nullable disable
namespace DysonNetwork.Sphere.Migrations
{
/// <inheritdoc />
public partial class AddRelationship : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "account_relationships",
columns: table => new
{
account_id = table.Column<long>(type: "bigint", nullable: false),
related_id = table.Column<long>(type: "bigint", nullable: false),
expired_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
status = table.Column<int>(type: "integer", nullable: false),
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("pk_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.CreateIndex(
name: "ix_account_relationships_related_id",
table: "account_relationships",
column: "related_id");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "account_relationships");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,451 +0,0 @@
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore.Migrations;
using NodaTime;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace DysonNetwork.Sphere.Migrations
{
/// <inheritdoc />
public partial class AddPost : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<long>(
name: "post_id",
table: "files",
type: "bigint",
nullable: true);
migrationBuilder.CreateTable(
name: "post_categories",
columns: table => new
{
id = table.Column<long>(type: "bigint", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
slug = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: false),
name = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("pk_post_categories", x => x.id);
});
migrationBuilder.CreateTable(
name: "post_tags",
columns: table => new
{
id = table.Column<long>(type: "bigint", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
slug = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: false),
name = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("pk_post_tags", x => x.id);
});
migrationBuilder.CreateTable(
name: "publishers",
columns: table => new
{
id = table.Column<long>(type: "bigint", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
publisher_type = table.Column<int>(type: "integer", nullable: false),
name = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
nick = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
bio = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: true),
picture_id = table.Column<string>(type: "text", nullable: true),
background_id = table.Column<string>(type: "text", nullable: true),
account_id = table.Column<long>(type: "bigint", nullable: true),
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true)
},
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");
});
migrationBuilder.CreateTable(
name: "post_collections",
columns: table => new
{
id = table.Column<long>(type: "bigint", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
slug = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: false),
name = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
description = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: true),
publisher_id = table.Column<long>(type: "bigint", nullable: false),
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("pk_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<long>(type: "bigint", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
title = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: true),
description = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: true),
language = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: true),
edited_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
published_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
visibility = table.Column<int>(type: "integer", nullable: false),
content = table.Column<string>(type: "text", nullable: true),
type = table.Column<int>(type: "integer", nullable: false),
meta = table.Column<Dictionary<string, object>>(type: "jsonb", nullable: true),
views_unique = table.Column<int>(type: "integer", nullable: false),
views_total = table.Column<int>(type: "integer", nullable: false),
upvotes = table.Column<int>(type: "integer", nullable: false),
downvotes = table.Column<int>(type: "integer", nullable: false),
threaded_post_id = table.Column<long>(type: "bigint", nullable: true),
replied_post_id = table.Column<long>(type: "bigint", nullable: true),
forwarded_post_id = table.Column<long>(type: "bigint", nullable: true),
publisher_id = table.Column<long>(type: "bigint", nullable: false),
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("pk_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_members",
columns: table => new
{
publisher_id = table.Column<long>(type: "bigint", nullable: false),
account_id = table.Column<long>(type: "bigint", nullable: false),
role = table.Column<int>(type: "integer", nullable: false),
joined_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true)
},
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: "post_category_links",
columns: table => new
{
categories_id = table.Column<long>(type: "bigint", nullable: false),
posts_id = table.Column<long>(type: "bigint", 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<long>(type: "bigint", nullable: false),
posts_id = table.Column<long>(type: "bigint", 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<long>(type: "bigint", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
symbol = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
attitude = table.Column<int>(type: "integer", nullable: false),
post_id = table.Column<long>(type: "bigint", nullable: false),
account_id = table.Column<long>(type: "bigint", nullable: false),
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("pk_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<long>(type: "bigint", nullable: false),
tags_id = table.Column<long>(type: "bigint", 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.CreateIndex(
name: "ix_files_post_id",
table: "files",
column: "post_id");
migrationBuilder.CreateIndex(
name: "ix_accounts_name",
table: "accounts",
column: "name",
unique: true);
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_threaded_post_id",
table: "posts",
column: "threaded_post_id",
unique: true);
migrationBuilder.CreateIndex(
name: "ix_publisher_members_account_id",
table: "publisher_members",
column: "account_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.AddForeignKey(
name: "fk_files_posts_post_id",
table: "files",
column: "post_id",
principalTable: "posts",
principalColumn: "id");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "fk_files_posts_post_id",
table: "files");
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_members");
migrationBuilder.DropTable(
name: "post_categories");
migrationBuilder.DropTable(
name: "post_collections");
migrationBuilder.DropTable(
name: "post_tags");
migrationBuilder.DropTable(
name: "posts");
migrationBuilder.DropTable(
name: "publishers");
migrationBuilder.DropIndex(
name: "ix_files_post_id",
table: "files");
migrationBuilder.DropIndex(
name: "ix_accounts_name",
table: "accounts");
migrationBuilder.DropColumn(
name: "post_id",
table: "files");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,201 +0,0 @@
using System;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore.Migrations;
using NodaTime;
#nullable disable
namespace DysonNetwork.Sphere.Migrations
{
/// <inheritdoc />
public partial class AddNotification : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<string>(
name: "picture_id",
table: "publishers",
type: "character varying(128)",
nullable: true,
oldClrType: typeof(string),
oldType: "text",
oldNullable: true);
migrationBuilder.AlterColumn<string>(
name: "background_id",
table: "publishers",
type: "character varying(128)",
nullable: true,
oldClrType: typeof(string),
oldType: "text",
oldNullable: true);
migrationBuilder.AlterColumn<string>(
name: "id",
table: "files",
type: "character varying(128)",
maxLength: 128,
nullable: false,
oldClrType: typeof(string),
oldType: "text");
migrationBuilder.AddColumn<Instant>(
name: "expired_at",
table: "files",
type: "timestamp with time zone",
nullable: true);
migrationBuilder.AlterColumn<string>(
name: "picture_id",
table: "account_profiles",
type: "character varying(128)",
nullable: true,
oldClrType: typeof(string),
oldType: "text",
oldNullable: true);
migrationBuilder.AlterColumn<string>(
name: "background_id",
table: "account_profiles",
type: "character varying(128)",
nullable: true,
oldClrType: typeof(string),
oldType: "text",
oldNullable: true);
migrationBuilder.CreateTable(
name: "notification_push_subscriptions",
columns: table => new
{
id = table.Column<Guid>(type: "uuid", nullable: false),
device_id = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: false),
device_token = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: false),
provider = table.Column<int>(type: "integer", nullable: false),
last_used_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
account_id = table.Column<long>(type: "bigint", nullable: false),
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("pk_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<Guid>(type: "uuid", nullable: false),
title = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: true),
subtitle = table.Column<string>(type: "character varying(2048)", maxLength: 2048, nullable: true),
content = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: true),
meta = table.Column<Dictionary<string, object>>(type: "jsonb", nullable: true),
priority = table.Column<int>(type: "integer", nullable: false),
viewed_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
account_id = table.Column<long>(type: "bigint", nullable: false),
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("pk_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.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");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "notification_push_subscriptions");
migrationBuilder.DropTable(
name: "notifications");
migrationBuilder.DropColumn(
name: "expired_at",
table: "files");
migrationBuilder.AlterColumn<string>(
name: "picture_id",
table: "publishers",
type: "text",
nullable: true,
oldClrType: typeof(string),
oldType: "character varying(128)",
oldNullable: true);
migrationBuilder.AlterColumn<string>(
name: "background_id",
table: "publishers",
type: "text",
nullable: true,
oldClrType: typeof(string),
oldType: "character varying(128)",
oldNullable: true);
migrationBuilder.AlterColumn<string>(
name: "id",
table: "files",
type: "text",
nullable: false,
oldClrType: typeof(string),
oldType: "character varying(128)",
oldMaxLength: 128);
migrationBuilder.AlterColumn<string>(
name: "picture_id",
table: "account_profiles",
type: "text",
nullable: true,
oldClrType: typeof(string),
oldType: "character varying(128)",
oldNullable: true);
migrationBuilder.AlterColumn<string>(
name: "background_id",
table: "account_profiles",
type: "text",
nullable: true,
oldClrType: typeof(string),
oldType: "character varying(128)",
oldNullable: true);
}
}
}

View File

@ -1,137 +0,0 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
using NodaTime;
#nullable disable
namespace DysonNetwork.Sphere.Migrations
{
/// <inheritdoc />
public partial class AddPermission : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "topic",
table: "notifications",
type: "character varying(1024)",
maxLength: 1024,
nullable: false,
defaultValue: "");
migrationBuilder.CreateTable(
name: "permission_groups",
columns: table => new
{
id = table.Column<Guid>(type: "uuid", nullable: false),
key = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false),
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("pk_permission_groups", x => x.id);
});
migrationBuilder.CreateTable(
name: "permission_group_member",
columns: table => new
{
group_id = table.Column<Guid>(type: "uuid", nullable: false),
account_id = table.Column<long>(type: "bigint", nullable: false),
expired_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
affected_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("pk_permission_group_member", x => new { x.group_id, x.account_id });
table.ForeignKey(
name: "fk_permission_group_member_accounts_account_id",
column: x => x.account_id,
principalTable: "accounts",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "fk_permission_group_member_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<Guid>(type: "uuid", nullable: false),
actor = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false),
area = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false),
key = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false),
value = table.Column<object>(type: "jsonb", nullable: false),
expired_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
affected_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
group_id = table.Column<Guid>(type: "uuid", nullable: true),
permission_group_id = table.Column<Guid>(type: "uuid", nullable: true),
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("pk_permission_nodes", x => x.id);
table.ForeignKey(
name: "fk_permission_nodes_permission_groups_permission_group_id",
column: x => x.permission_group_id,
principalTable: "permission_groups",
principalColumn: "id");
table.ForeignKey(
name: "fk_permission_nodes_permission_nodes_group_id",
column: x => x.group_id,
principalTable: "permission_nodes",
principalColumn: "id");
});
migrationBuilder.CreateIndex(
name: "ix_permission_group_member_account_id",
table: "permission_group_member",
column: "account_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_permission_nodes_permission_group_id",
table: "permission_nodes",
column: "permission_group_id");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "permission_group_member");
migrationBuilder.DropTable(
name: "permission_nodes");
migrationBuilder.DropTable(
name: "permission_groups");
migrationBuilder.DropColumn(
name: "topic",
table: "notifications");
}
}
}

View File

@ -1,6 +1,7 @@
// <auto-generated />
using System;
using System.Collections.Generic;
using System.Text.Json;
using DysonNetwork.Sphere;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
@ -14,8 +15,8 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
namespace DysonNetwork.Sphere.Migrations
{
[DbContext(typeof(AppDatabase))]
[Migration("20250427161252_AddPermission")]
partial class AddPermission
[Migration("20250429115700_InitialMigration")]
partial class InitialMigration
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
@ -36,6 +37,10 @@ namespace DysonNetwork.Sphere.Migrations
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
b.Property<Instant?>("ActivatedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("activated_at");
b.Property<Instant>("CreatedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("created_at");
@ -170,6 +175,65 @@ namespace DysonNetwork.Sphere.Migrations
b.ToTable("account_contacts", (string)null);
});
modelBuilder.Entity("DysonNetwork.Sphere.Account.MagicSpell", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid")
.HasColumnName("id");
b.Property<long?>("AccountId")
.HasColumnType("bigint")
.HasColumnName("account_id");
b.Property<Instant?>("AffectedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("affected_at");
b.Property<Instant>("CreatedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("created_at");
b.Property<Instant?>("DeletedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("deleted_at");
b.Property<Instant?>("ExpiresAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("expires_at");
b.Property<Dictionary<string, object>>("Meta")
.IsRequired()
.HasColumnType("jsonb")
.HasColumnName("meta");
b.Property<string>("Spell")
.IsRequired()
.HasMaxLength(1024)
.HasColumnType("character varying(1024)")
.HasColumnName("spell");
b.Property<int>("Type")
.HasColumnType("integer")
.HasColumnName("type");
b.Property<Instant>("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<Guid>("Id")
@ -430,6 +494,10 @@ namespace DysonNetwork.Sphere.Migrations
.HasColumnType("timestamp with time zone")
.HasColumnName("expired_at");
b.Property<int>("FailedAttempts")
.HasColumnType("integer")
.HasColumnName("failed_attempts");
b.Property<string>("IpAddress")
.HasMaxLength(128)
.HasColumnType("character varying(128)")
@ -555,9 +623,10 @@ namespace DysonNetwork.Sphere.Migrations
.HasColumnType("uuid")
.HasColumnName("group_id");
b.Property<long>("AccountId")
.HasColumnType("bigint")
.HasColumnName("account_id");
b.Property<string>("Actor")
.HasMaxLength(1024)
.HasColumnType("character varying(1024)")
.HasColumnName("actor");
b.Property<Instant?>("AffectedAt")
.HasColumnType("timestamp with time zone")
@ -579,13 +648,10 @@ namespace DysonNetwork.Sphere.Migrations
.HasColumnType("timestamp with time zone")
.HasColumnName("updated_at");
b.HasKey("GroupId", "AccountId")
.HasName("pk_permission_group_member");
b.HasKey("GroupId", "Actor")
.HasName("pk_permission_group_members");
b.HasIndex("AccountId")
.HasDatabaseName("ix_permission_group_member_account_id");
b.ToTable("permission_group_member", (string)null);
b.ToTable("permission_group_members", (string)null);
});
modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionNode", b =>
@ -633,15 +699,11 @@ namespace DysonNetwork.Sphere.Migrations
.HasColumnType("character varying(1024)")
.HasColumnName("key");
b.Property<Guid?>("PermissionGroupId")
.HasColumnType("uuid")
.HasColumnName("permission_group_id");
b.Property<Instant>("UpdatedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("updated_at");
b.Property<object>("Value")
b.Property<JsonDocument>("Value")
.IsRequired()
.HasColumnType("jsonb")
.HasColumnName("value");
@ -652,9 +714,6 @@ namespace DysonNetwork.Sphere.Migrations
b.HasIndex("GroupId")
.HasDatabaseName("ix_permission_nodes_group_id");
b.HasIndex("PermissionGroupId")
.HasDatabaseName("ix_permission_nodes_permission_group_id");
b.HasIndex("Key", "Area", "Actor")
.HasDatabaseName("ix_permission_nodes_key_area_actor");
@ -1231,6 +1290,16 @@ namespace DysonNetwork.Sphere.Migrations
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")
@ -1337,36 +1406,22 @@ namespace DysonNetwork.Sphere.Migrations
modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroupMember", b =>
{
b.HasOne("DysonNetwork.Sphere.Account.Account", "Account")
.WithMany("GroupMemberships")
.HasForeignKey("AccountId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("fk_permission_group_member_accounts_account_id");
b.HasOne("DysonNetwork.Sphere.Permission.PermissionGroup", "Group")
.WithMany("Members")
.HasForeignKey("GroupId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("fk_permission_group_member_permission_groups_group_id");
b.Navigation("Account");
.HasConstraintName("fk_permission_group_members_permission_groups_group_id");
b.Navigation("Group");
});
modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionNode", b =>
{
b.HasOne("DysonNetwork.Sphere.Permission.PermissionNode", "Group")
.WithMany()
.HasForeignKey("GroupId")
.HasConstraintName("fk_permission_nodes_permission_nodes_group_id");
b.HasOne("DysonNetwork.Sphere.Permission.PermissionGroup", null)
b.HasOne("DysonNetwork.Sphere.Permission.PermissionGroup", "Group")
.WithMany("Nodes")
.HasForeignKey("PermissionGroupId")
.HasConstraintName("fk_permission_nodes_permission_groups_permission_group_id");
.HasForeignKey("GroupId")
.HasConstraintName("fk_permission_nodes_permission_groups_group_id");
b.Navigation("Group");
});
@ -1560,8 +1615,6 @@ namespace DysonNetwork.Sphere.Migrations
b.Navigation("Contacts");
b.Navigation("GroupMemberships");
b.Navigation("IncomingRelationships");
b.Navigation("OutgoingRelationships");

View File

@ -0,0 +1,952 @@
using System;
using System.Collections.Generic;
using System.Text.Json;
using Microsoft.EntityFrameworkCore.Migrations;
using NodaTime;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace DysonNetwork.Sphere.Migrations
{
/// <inheritdoc />
public partial class InitialMigration : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "accounts",
columns: table => new
{
id = table.Column<long>(type: "bigint", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
name = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
nick = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
language = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: false),
activated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
is_superuser = table.Column<bool>(type: "boolean", nullable: false),
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("pk_accounts", x => x.id);
});
migrationBuilder.CreateTable(
name: "permission_groups",
columns: table => new
{
id = table.Column<Guid>(type: "uuid", nullable: false),
key = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false),
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("pk_permission_groups", x => x.id);
});
migrationBuilder.CreateTable(
name: "post_categories",
columns: table => new
{
id = table.Column<long>(type: "bigint", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
slug = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: false),
name = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("pk_post_categories", x => x.id);
});
migrationBuilder.CreateTable(
name: "post_tags",
columns: table => new
{
id = table.Column<long>(type: "bigint", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
slug = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: false),
name = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("pk_post_tags", x => x.id);
});
migrationBuilder.CreateTable(
name: "account_auth_factors",
columns: table => new
{
id = table.Column<long>(type: "bigint", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
type = table.Column<int>(type: "integer", nullable: false),
secret = table.Column<string>(type: "text", nullable: true),
account_id = table.Column<long>(type: "bigint", nullable: false),
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("pk_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_contacts",
columns: table => new
{
id = table.Column<long>(type: "bigint", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
type = table.Column<int>(type: "integer", nullable: false),
verified_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
content = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false),
account_id = table.Column<long>(type: "bigint", nullable: false),
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("pk_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<long>(type: "bigint", nullable: false),
related_id = table.Column<long>(type: "bigint", nullable: false),
expired_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
status = table.Column<int>(type: "integer", nullable: false),
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("pk_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: "auth_challenges",
columns: table => new
{
id = table.Column<Guid>(type: "uuid", nullable: false),
expired_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
step_remain = table.Column<int>(type: "integer", nullable: false),
step_total = table.Column<int>(type: "integer", nullable: false),
failed_attempts = table.Column<int>(type: "integer", nullable: false),
blacklist_factors = table.Column<List<long>>(type: "jsonb", nullable: false),
audiences = table.Column<List<string>>(type: "jsonb", nullable: false),
scopes = table.Column<List<string>>(type: "jsonb", nullable: false),
ip_address = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: true),
user_agent = table.Column<string>(type: "character varying(512)", maxLength: 512, nullable: true),
device_id = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
nonce = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: true),
account_id = table.Column<long>(type: "bigint", nullable: false),
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("pk_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: "magic_spells",
columns: table => new
{
id = table.Column<Guid>(type: "uuid", nullable: false),
spell = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false),
type = table.Column<int>(type: "integer", nullable: false),
expires_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
affected_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
meta = table.Column<Dictionary<string, object>>(type: "jsonb", nullable: false),
account_id = table.Column<long>(type: "bigint", nullable: true),
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true)
},
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<Guid>(type: "uuid", nullable: false),
device_id = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: false),
device_token = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: false),
provider = table.Column<int>(type: "integer", nullable: false),
last_used_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
account_id = table.Column<long>(type: "bigint", nullable: false),
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("pk_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<Guid>(type: "uuid", nullable: false),
topic = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false),
title = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: true),
subtitle = table.Column<string>(type: "character varying(2048)", maxLength: 2048, nullable: true),
content = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: true),
meta = table.Column<Dictionary<string, object>>(type: "jsonb", nullable: true),
priority = table.Column<int>(type: "integer", nullable: false),
viewed_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
account_id = table.Column<long>(type: "bigint", nullable: false),
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("pk_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: "permission_group_members",
columns: table => new
{
group_id = table.Column<Guid>(type: "uuid", nullable: false),
actor = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false),
expired_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
affected_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true)
},
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<Guid>(type: "uuid", nullable: false),
actor = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false),
area = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false),
key = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false),
value = table.Column<JsonDocument>(type: "jsonb", nullable: false),
expired_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
affected_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
group_id = table.Column<Guid>(type: "uuid", nullable: true),
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true)
},
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<Guid>(type: "uuid", nullable: false),
last_granted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
expired_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
account_id = table.Column<long>(type: "bigint", nullable: false),
challenge_id = table.Column<Guid>(type: "uuid", nullable: false),
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("pk_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: "account_profiles",
columns: table => new
{
id = table.Column<long>(type: "bigint", nullable: false),
first_name = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
middle_name = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
last_name = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
bio = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: true),
picture_id = table.Column<string>(type: "character varying(128)", nullable: true),
background_id = table.Column<string>(type: "character varying(128)", nullable: true),
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true)
},
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: "files",
columns: table => new
{
id = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: false),
name = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false),
description = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: true),
file_meta = table.Column<Dictionary<string, object>>(type: "jsonb", nullable: true),
user_meta = table.Column<Dictionary<string, object>>(type: "jsonb", nullable: true),
mime_type = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
hash = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
size = table.Column<long>(type: "bigint", nullable: false),
uploaded_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
expired_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
uploaded_to = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: true),
used_count = table.Column<int>(type: "integer", nullable: false),
account_id = table.Column<long>(type: "bigint", nullable: false),
post_id = table.Column<long>(type: "bigint", nullable: true),
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true)
},
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);
});
migrationBuilder.CreateTable(
name: "publishers",
columns: table => new
{
id = table.Column<long>(type: "bigint", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
publisher_type = table.Column<int>(type: "integer", nullable: false),
name = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
nick = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
bio = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: true),
picture_id = table.Column<string>(type: "character varying(128)", nullable: true),
background_id = table.Column<string>(type: "character varying(128)", nullable: true),
account_id = table.Column<long>(type: "bigint", nullable: true),
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true)
},
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");
});
migrationBuilder.CreateTable(
name: "post_collections",
columns: table => new
{
id = table.Column<long>(type: "bigint", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
slug = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: false),
name = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
description = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: true),
publisher_id = table.Column<long>(type: "bigint", nullable: false),
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("pk_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<long>(type: "bigint", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
title = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: true),
description = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: true),
language = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: true),
edited_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
published_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
visibility = table.Column<int>(type: "integer", nullable: false),
content = table.Column<string>(type: "text", nullable: true),
type = table.Column<int>(type: "integer", nullable: false),
meta = table.Column<Dictionary<string, object>>(type: "jsonb", nullable: true),
views_unique = table.Column<int>(type: "integer", nullable: false),
views_total = table.Column<int>(type: "integer", nullable: false),
upvotes = table.Column<int>(type: "integer", nullable: false),
downvotes = table.Column<int>(type: "integer", nullable: false),
threaded_post_id = table.Column<long>(type: "bigint", nullable: true),
replied_post_id = table.Column<long>(type: "bigint", nullable: true),
forwarded_post_id = table.Column<long>(type: "bigint", nullable: true),
publisher_id = table.Column<long>(type: "bigint", nullable: false),
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("pk_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_members",
columns: table => new
{
publisher_id = table.Column<long>(type: "bigint", nullable: false),
account_id = table.Column<long>(type: "bigint", nullable: false),
role = table.Column<int>(type: "integer", nullable: false),
joined_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true)
},
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: "post_category_links",
columns: table => new
{
categories_id = table.Column<long>(type: "bigint", nullable: false),
posts_id = table.Column<long>(type: "bigint", 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<long>(type: "bigint", nullable: false),
posts_id = table.Column<long>(type: "bigint", 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<long>(type: "bigint", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
symbol = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
attitude = table.Column<int>(type: "integer", nullable: false),
post_id = table.Column<long>(type: "bigint", nullable: false),
account_id = table.Column<long>(type: "bigint", nullable: false),
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("pk_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<long>(type: "bigint", nullable: false),
tags_id = table.Column<long>(type: "bigint", 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.CreateIndex(
name: "ix_account_auth_factors_account_id",
table: "account_auth_factors",
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_accounts_name",
table: "accounts",
column: "name",
unique: true);
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_files_account_id",
table: "files",
column: "account_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_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_threaded_post_id",
table: "posts",
column: "threaded_post_id",
unique: true);
migrationBuilder.CreateIndex(
name: "ix_publisher_members_account_id",
table: "publisher_members",
column: "account_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.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_files_posts_post_id",
table: "files",
column: "post_id",
principalTable: "posts",
principalColumn: "id");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "fk_files_accounts_account_id",
table: "files");
migrationBuilder.DropForeignKey(
name: "fk_publishers_accounts_account_id",
table: "publishers");
migrationBuilder.DropForeignKey(
name: "fk_publishers_files_background_id",
table: "publishers");
migrationBuilder.DropForeignKey(
name: "fk_publishers_files_picture_id",
table: "publishers");
migrationBuilder.DropTable(
name: "account_auth_factors");
migrationBuilder.DropTable(
name: "account_contacts");
migrationBuilder.DropTable(
name: "account_profiles");
migrationBuilder.DropTable(
name: "account_relationships");
migrationBuilder.DropTable(
name: "auth_sessions");
migrationBuilder.DropTable(
name: "magic_spells");
migrationBuilder.DropTable(
name: "notification_push_subscriptions");
migrationBuilder.DropTable(
name: "notifications");
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_members");
migrationBuilder.DropTable(
name: "auth_challenges");
migrationBuilder.DropTable(
name: "permission_groups");
migrationBuilder.DropTable(
name: "post_categories");
migrationBuilder.DropTable(
name: "post_collections");
migrationBuilder.DropTable(
name: "post_tags");
migrationBuilder.DropTable(
name: "accounts");
migrationBuilder.DropTable(
name: "files");
migrationBuilder.DropTable(
name: "posts");
migrationBuilder.DropTable(
name: "publishers");
}
}
}

View File

@ -1,6 +1,7 @@
// <auto-generated />
using System;
using System.Collections.Generic;
using System.Text.Json;
using DysonNetwork.Sphere;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
@ -33,6 +34,10 @@ namespace DysonNetwork.Sphere.Migrations
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
b.Property<Instant?>("ActivatedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("activated_at");
b.Property<Instant>("CreatedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("created_at");
@ -167,6 +172,65 @@ namespace DysonNetwork.Sphere.Migrations
b.ToTable("account_contacts", (string)null);
});
modelBuilder.Entity("DysonNetwork.Sphere.Account.MagicSpell", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid")
.HasColumnName("id");
b.Property<long?>("AccountId")
.HasColumnType("bigint")
.HasColumnName("account_id");
b.Property<Instant?>("AffectedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("affected_at");
b.Property<Instant>("CreatedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("created_at");
b.Property<Instant?>("DeletedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("deleted_at");
b.Property<Instant?>("ExpiresAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("expires_at");
b.Property<Dictionary<string, object>>("Meta")
.IsRequired()
.HasColumnType("jsonb")
.HasColumnName("meta");
b.Property<string>("Spell")
.IsRequired()
.HasMaxLength(1024)
.HasColumnType("character varying(1024)")
.HasColumnName("spell");
b.Property<int>("Type")
.HasColumnType("integer")
.HasColumnName("type");
b.Property<Instant>("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<Guid>("Id")
@ -427,6 +491,10 @@ namespace DysonNetwork.Sphere.Migrations
.HasColumnType("timestamp with time zone")
.HasColumnName("expired_at");
b.Property<int>("FailedAttempts")
.HasColumnType("integer")
.HasColumnName("failed_attempts");
b.Property<string>("IpAddress")
.HasMaxLength(128)
.HasColumnType("character varying(128)")
@ -552,9 +620,10 @@ namespace DysonNetwork.Sphere.Migrations
.HasColumnType("uuid")
.HasColumnName("group_id");
b.Property<long>("AccountId")
.HasColumnType("bigint")
.HasColumnName("account_id");
b.Property<string>("Actor")
.HasMaxLength(1024)
.HasColumnType("character varying(1024)")
.HasColumnName("actor");
b.Property<Instant?>("AffectedAt")
.HasColumnType("timestamp with time zone")
@ -576,13 +645,10 @@ namespace DysonNetwork.Sphere.Migrations
.HasColumnType("timestamp with time zone")
.HasColumnName("updated_at");
b.HasKey("GroupId", "AccountId")
.HasName("pk_permission_group_member");
b.HasKey("GroupId", "Actor")
.HasName("pk_permission_group_members");
b.HasIndex("AccountId")
.HasDatabaseName("ix_permission_group_member_account_id");
b.ToTable("permission_group_member", (string)null);
b.ToTable("permission_group_members", (string)null);
});
modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionNode", b =>
@ -630,15 +696,11 @@ namespace DysonNetwork.Sphere.Migrations
.HasColumnType("character varying(1024)")
.HasColumnName("key");
b.Property<Guid?>("PermissionGroupId")
.HasColumnType("uuid")
.HasColumnName("permission_group_id");
b.Property<Instant>("UpdatedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("updated_at");
b.Property<object>("Value")
b.Property<JsonDocument>("Value")
.IsRequired()
.HasColumnType("jsonb")
.HasColumnName("value");
@ -649,9 +711,6 @@ namespace DysonNetwork.Sphere.Migrations
b.HasIndex("GroupId")
.HasDatabaseName("ix_permission_nodes_group_id");
b.HasIndex("PermissionGroupId")
.HasDatabaseName("ix_permission_nodes_permission_group_id");
b.HasIndex("Key", "Area", "Actor")
.HasDatabaseName("ix_permission_nodes_key_area_actor");
@ -1228,6 +1287,16 @@ namespace DysonNetwork.Sphere.Migrations
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")
@ -1334,36 +1403,22 @@ namespace DysonNetwork.Sphere.Migrations
modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroupMember", b =>
{
b.HasOne("DysonNetwork.Sphere.Account.Account", "Account")
.WithMany("GroupMemberships")
.HasForeignKey("AccountId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("fk_permission_group_member_accounts_account_id");
b.HasOne("DysonNetwork.Sphere.Permission.PermissionGroup", "Group")
.WithMany("Members")
.HasForeignKey("GroupId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("fk_permission_group_member_permission_groups_group_id");
b.Navigation("Account");
.HasConstraintName("fk_permission_group_members_permission_groups_group_id");
b.Navigation("Group");
});
modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionNode", b =>
{
b.HasOne("DysonNetwork.Sphere.Permission.PermissionNode", "Group")
.WithMany()
.HasForeignKey("GroupId")
.HasConstraintName("fk_permission_nodes_permission_nodes_group_id");
b.HasOne("DysonNetwork.Sphere.Permission.PermissionGroup", null)
b.HasOne("DysonNetwork.Sphere.Permission.PermissionGroup", "Group")
.WithMany("Nodes")
.HasForeignKey("PermissionGroupId")
.HasConstraintName("fk_permission_nodes_permission_groups_permission_group_id");
.HasForeignKey("GroupId")
.HasConstraintName("fk_permission_nodes_permission_groups_group_id");
b.Navigation("Group");
});
@ -1557,8 +1612,6 @@ namespace DysonNetwork.Sphere.Migrations
b.Navigation("Contacts");
b.Navigation("GroupMemberships");
b.Navigation("IncomingRelationships");
b.Navigation("OutgoingRelationships");

View File

@ -125,8 +125,7 @@
<br/>
Hosted by
<a
@* TODO Update the project link here *@
href="https://github.com/Solsynth/HyperNet.Nexus"
href="https://github.com/Solsynth/DysonNetwork"
style="color: #c9d1d9"
>DysonNetwork.Sphere</a>
</div>

View File

@ -0,0 +1,98 @@
@page "/spells/{spellWord}"
@using DysonNetwork.Sphere.Account
@model DysonNetwork.Sphere.Pages.MagicSpellPage
@{
Layout = null;
var spell = ViewData["Spell"] as MagicSpell;
}
<!DOCTYPE html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Solar Network Magic Spell</title>
<link
href="https://fonts.googleapis.com/css2?family=Roboto+Mono&display=swap"
rel="stylesheet"
/>
<style>
body {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
height: 100vh;
background-color: #2d2d2d;
font-family: "Roboto Mono", monospace;
color: #c9d1d9;
}
.parent {
padding: 20px;
max-width: 480px;
margin: 0 auto;
}
h2 {
font-size: 18px;
font-weight: 300;
color: #ffffff;
margin-bottom: 15px;
}
.footer {
margin-top: 20px;
font-size: 11px;
opacity: 0.6;
}
.footer-product {
font-size: 12px;
font-weight: bold;
margin-bottom: 5px;
opacity: 0.8;
}
.g-recaptcha {
display: inline-block; /* Adjust as needed */
}
</style>
</head>
<body>
<div class="parent">
<div class="container">
<h1>Magic Spell</h1>
@if (spell == null)
{
<div>
<p style="color: yellow;">The spell was expired or not exists.</p>
</div>
}
else
{
<div>
<p style="color: green;">The spell was applied successfully!</p>
</div>
}
</div>
<div class="footer">
<div class="footer-product">Solar Network</div>
<a
href="https://solsynth.dev"
style="color: #c9d1d9; text-decoration: none"
>Solsynth LLC</a>
&copy; @DateTime.Now.Year<br/>
Powered by
<a
href="https://github.com/Solsynth/DysonNetwork"
style="color: #c9d1d9"
>DysonNetwork.Sphere</a>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,24 @@
using DysonNetwork.Sphere.Account;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using NodaTime;
namespace DysonNetwork.Sphere.Pages;
public class MagicSpellPage(AppDatabase db, MagicSpellService spells) : PageModel
{
public async Task<ActionResult> OnGet(string spellWord)
{
var now = SystemClock.Instance.GetCurrentInstant();
var spell = await db.MagicSpells
.Where(e => e.Spell == spellWord)
.Where(e => e.ExpiresAt == null || now >= e.ExpiresAt)
.Where(e => e.AffectedAt == null || now >= e.AffectedAt)
.FirstOrDefaultAsync();
ViewData["Spell"] = spell;
return Page();
}
}

View File

@ -1,5 +1,6 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Text.Json;
using Microsoft.EntityFrameworkCore;
using Newtonsoft.Json;
using NodaTime;
@ -18,18 +19,24 @@ namespace DysonNetwork.Sphere.Permission;
/// 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
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 object Value { 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 PermissionNode? Group { get; set; } = null;
[JsonIgnore] public PermissionGroup? Group { get; set; } = null;
public void Dispose()
{
Value.Dispose();
GC.SuppressFinalize(this);
}
}
public class PermissionGroup : ModelBase
@ -44,10 +51,9 @@ public class PermissionGroup : ModelBase
public class PermissionGroupMember : ModelBase
{
public Guid GroupId { get; set; }
public long AccountId { get; set; }
public PermissionGroup Group { get; set; } = null!;
public Account.Account Account { get; set; } = null!;
public Instant? ExpiredAt { get; set; } = null;
public Instant? AffectedAt { get; set; } = null;
[MaxLength(1024)] public string Actor { get; set; } = null!;
public Instant? ExpiredAt { get; set; }
public Instant? AffectedAt { get; set; }
}

View File

@ -0,0 +1,51 @@
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<RequiredPermissionAttribute>()
.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<bool>(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);
}
}

View File

@ -0,0 +1,126 @@
using System.Text.Json;
using Microsoft.EntityFrameworkCore;
using NodaTime;
namespace DysonNetwork.Sphere.Permission;
public class PermissionService(AppDatabase db)
{
public async Task<T?> GetPermissionAsync<T>(string actor, string area, string key)
{
var now = SystemClock.Instance.GetCurrentInstant();
var 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();
var permission = await db.PermissionNodes
.Where(n => n.GroupId == null || groupsId.Contains(n.GroupId.Value))
.Where(n => n.Key == key && n.Actor == actor && n.Area == area)
.Where(n => n.ExpiredAt == null || n.ExpiredAt < now)
.Where(n => n.AffectedAt == null || n.AffectedAt >= now)
.FirstOrDefaultAsync();
return permission is not null ? _DeserializePermissionValue<T>(permission.Value) : default;
}
public async Task<PermissionNode> AddPermissionNode<T>(
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();
return node;
}
public async Task<PermissionNode> AddPermissionNodeToGroup<T>(
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();
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();
}
public async Task RemovePermissionNodeFromGroup<T>(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();
}
private static T? _DeserializePermissionValue<T>(JsonDocument json)
{
return JsonSerializer.Deserialize<T>(json.RootElement.GetRawText());
}
private static JsonDocument _SerializePermissionValue<T>(T obj)
{
var str = JsonSerializer.Serialize(obj);
return JsonDocument.Parse(str);
}
public static PermissionNode NewPermissionNode<T>(string actor, string area, string key, T value)
{
return new PermissionNode
{
Actor = actor,
Area = area,
Key = key,
Value = _SerializePermissionValue(value),
};
}
}

View File

@ -1,5 +1,6 @@
using System.ComponentModel.DataAnnotations;
using Casbin;
using DysonNetwork.Sphere.Permission;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using NodaTime;
@ -8,7 +9,7 @@ namespace DysonNetwork.Sphere.Post;
[ApiController]
[Route("/posts")]
public class PostController(AppDatabase db, PostService ps, IEnforcer enforcer) : ControllerBase
public class PostController(AppDatabase db, PostService ps) : ControllerBase
{
[HttpGet]
public async Task<ActionResult<List<Post>>> ListPosts([FromQuery] int offset = 0, [FromQuery] int take = 20)
@ -118,14 +119,13 @@ public class PostController(AppDatabase db, PostService ps, IEnforcer enforcer)
}
[HttpPost]
[RequiredPermission("global", "posts.create")]
public async Task<ActionResult<Post>> CreatePost(
[FromBody] PostRequest request,
[FromHeader(Name = "X-Pub")] string? publisherName
)
{
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
if (!await enforcer.EnforceAsync((string)HttpContext.Items["CurrentIdentity"]!, "global", "posts", "create"))
return StatusCode(403);
Publisher? publisher;
if (publisherName is null)

View File

@ -1,5 +1,6 @@
using System.ComponentModel.DataAnnotations;
using Casbin;
using DysonNetwork.Sphere.Permission;
using DysonNetwork.Sphere.Storage;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
@ -10,7 +11,7 @@ namespace DysonNetwork.Sphere.Post;
[ApiController]
[Route("/publishers")]
public class PublisherController(AppDatabase db, PublisherService ps, FileService fs, IEnforcer enforcer)
public class PublisherController(AppDatabase db, PublisherService ps, FileService fs)
: ControllerBase
{
[HttpGet("{name}")]
@ -167,12 +168,10 @@ public class PublisherController(AppDatabase db, PublisherService ps, FileServic
[HttpPost("individual")]
[Authorize]
[RequiredPermission("global", "publishers.create")]
public async Task<ActionResult<Publisher>> CreatePublisherIndividual(PublisherRequest request)
{
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
if (!await enforcer.EnforceAsync((string)HttpContext.Items["CurrentIdentity"]!, "global", "publishers",
"create"))
return StatusCode(403);
var takenName = request.Name ?? currentUser.Name;
var duplicateNameCount = await db.Publishers

View File

@ -8,6 +8,7 @@ using Casbin.Persist.Adapter.EFCore;
using DysonNetwork.Sphere;
using DysonNetwork.Sphere.Account;
using DysonNetwork.Sphere.Auth;
using DysonNetwork.Sphere.Permission;
using DysonNetwork.Sphere.Post;
using DysonNetwork.Sphere.Storage;
using Microsoft.AspNetCore.Authorization;
@ -45,22 +46,6 @@ builder.Services.AddControllers().AddJsonOptions(options =>
});
builder.Services.AddRazorPages();
// Casbin permissions
var casbinDbContext = new CasbinDbContext<int>(
new DbContextOptionsBuilder<CasbinDbContext<int>>()
.UseNpgsql(builder.Configuration.GetConnectionString("Guard"))
.Options
);
var casbinEfcore = new EFCoreAdapter<int>(casbinDbContext);
casbinDbContext.Database.EnsureCreated();
var casbinEncofcer = new Enforcer("Casbin.conf", casbinEfcore);
casbinEncofcer.EnableCache(true);
casbinEncofcer.LoadPolicy();
builder.Services.AddSingleton<IEnforcer>(casbinEncofcer);
builder.Services.AddSingleton<IAuthorizationHandler, CasbinAuthorizationHandler>();
// Other pipelines
builder.Services.AddRateLimiter(o => o.AddFixedWindowLimiter(policyName: "fixed", opts =>
@ -129,7 +114,10 @@ builder.Services.AddSwaggerGen(options =>
});
builder.Services.AddOpenApi();
builder.Services.AddScoped<EmailService>();
builder.Services.AddScoped<PermissionService>();
builder.Services.AddScoped<AccountService>();
builder.Services.AddScoped<MagicSpellService>();
builder.Services.AddScoped<NotificationService>();
builder.Services.AddScoped<AuthService>();
builder.Services.AddScoped<FileService>();
@ -163,7 +151,7 @@ var app = builder.Build();
using (var scope = app.Services.CreateScope())
{
var db = scope.ServiceProvider.GetRequiredService<AppDatabase>();
db.Database.Migrate();
await db.Database.MigrateAsync();
}
app.MapOpenApi();
@ -189,6 +177,7 @@ app.UseRateLimiter();
app.UseHttpsRedirection();
app.UseAuthorization();
app.UseMiddleware<UserInfoMiddleware>();
app.UseMiddleware<PermissionMiddleware>();
app.MapControllers().RequireRateLimiting("fixed");
app.MapStaticAssets().RequireRateLimiting("fixed");

View File

@ -7,8 +7,7 @@
},
"AllowedHosts": "*",
"ConnectionStrings": {
"App": "Host=localhost;Port=5432;Database=dyson_network;Username=postgres;Password=postgres",
"Guard": "Host=localhost;Port=5432;Database=dyson_network_casbin;Username=postgres;Password=postgres"
"App": "Host=localhost;Port=5432;Database=dyson_network;Username=postgres;Password=postgres"
},
"Authentication": {
"Schemes": {
@ -55,11 +54,20 @@
"Production": true,
"Google": "path/to/config.json",
"Apple": {
"PrivateKey": "path/to/cert.p8",
"PrivateKey": "path/to/cert.p8",
"PrivateKeyId": "",
"TeamId": "",
"BundleIdentifier": ""
}
}
},
"Email": {
"Server": "",
"Port": 465,
"Username": "",
"Password": "",
"FromAddress": "",
"FromName": "",
"SubjectPrefix": "Solar Network"
}
}

View File

@ -2,6 +2,7 @@
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AApnSender_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F6aadc2cf048f477d8636fb2def7b73648200_003Fc5_003F2a1973a9_003FApnSender_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AAuthenticationMiddleware_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fe49de78932194d52a02b07486c6d023a24600_003F2f_003F7ab1cc57_003FAuthenticationMiddleware_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AAuthorizationAppBuilderExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F2ff26593f91746d7a53418a46dc419d1f200_003F4b_003F56550da2_003FAuthorizationAppBuilderExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ABodyBuilder_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003Fc5c8aba04a29d49c65d772c9ffcd93ac7eb38ccbb49a5f506518a0b9bdcaa75_003FBodyBuilder_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ABucketArgs_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003Fd515fb889657fcdcace3fed90735057b458ff9e0bb60bded7c8fe8b3a4673c_003FBucketArgs_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AChapterData_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Ffef366b36a224d469ff150d30f9a866d23c00_003Fe6_003F64a6c0f7_003FChapterData_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AClaim_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fa7fdc52b6e574ae7b9822133be91162a15800_003Ff7_003Feebffd8d_003FClaim_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
@ -28,6 +29,7 @@
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIServiceCollectionQuartzConfigurator_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F1edbd6e24d7b430fabce72177269baa19200_003F67_003Faee36f5b_003FIServiceCollectionQuartzConfigurator_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AITusStore_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F8bb08a178b5b43c5bac20a5a54159a5b2a800_003Fb1_003F7e861de5_003FITusStore_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AKestrelServerLimits_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F1e2e5dfcafad4407b569dd5df56a2fbf274e00_003Fa4_003F39445f62_003FKestrelServerLimits_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AMailboxAddress_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F8e03e47c46b7469f97abc40667cbcf9b133000_003Fa6_003F83324248_003FMailboxAddress_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AMediaAnalysis_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Ffef366b36a224d469ff150d30f9a866d23c00_003Fd7_003F5c138865_003FMediaAnalysis_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AMicrosoftDependencyInjectionJobFactory_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F1edbd6e24d7b430fabce72177269baa19200_003Fa8_003F91b091de_003FMicrosoftDependencyInjectionJobFactory_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ANotFoundResult_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F0b5acdd962e549369896cece0026e556214600_003F28_003F290250f5_003FNotFoundResult_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>