Compare commits
137 Commits
fa1a40c637
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
4443da5660
|
|||
|
b193224a2c
|
|||
|
4e9c5733d1
|
|||
|
1c6b324b0d
|
|||
|
ded3a70cb7
|
|||
|
9e54b61eee
|
|||
|
43d89299c3
|
|||
|
1af11b2a99
|
|||
|
1a31d7cbe7
|
|||
|
f0d6772dca
|
|||
|
24836fc606
|
|||
|
0bc77b948c
|
|||
|
f792d43ab9
|
|||
|
1b45be225a
|
|||
|
7811545726
|
|||
|
213608d4f0
|
|||
|
bca6a2ffde
|
|||
|
885b895a3a
|
|||
|
08941a282b
|
|||
|
4fd455acbf
|
|||
|
5ff1539f18
|
|||
|
3c023a71b1
|
|||
|
49d8eaa7b2
|
|||
|
16a37549fe
|
|||
|
2aff62c64f
|
|||
|
a49d485943
|
|||
|
4c65602465
|
|||
|
4242953969
|
|||
|
c9530ac8b5
|
|||
|
4ba7d38d78
|
|||
|
8642737a07
|
|||
|
8181938aaf
|
|||
|
922afc2239
|
|||
|
a071bd2738
|
|||
|
43945fc524
|
|||
|
e477429a35
|
|||
|
fe3a057185
|
|||
|
ad3c104c5c
|
|||
|
2020d625aa
|
|||
|
f471c5635d
|
|||
|
eaeaa28c60
|
|||
|
ee5c7cb7ce
|
|||
|
33abf12e41
|
|||
|
4a71f92ef0
|
|||
|
4faa1a4b64
|
|||
|
e49a1ec49a
|
|||
|
a88f42b26a
|
|||
|
c45be62331
|
|||
|
c8228e0c8e
|
|||
|
c642c6d646
|
|||
|
270c211cb8
|
|||
|
74c8f3490d
|
|||
|
b364edc74b
|
|||
|
9addf38677
|
|||
|
a02ed10434
|
|||
|
aca28f9318
|
|||
|
c2f72993b7
|
|||
|
158cc75c5b
|
|||
|
fa2f53ff7a
|
|||
|
2cce5ebf80
|
|||
|
13b2e46ecc
|
|||
|
cbd68c9ae6
|
|||
|
b99b61e0f9
|
|||
|
94f4e68120
|
|||
|
d5510f7e4d
|
|||
|
c038ab9e3c
|
|||
|
e97719ec84
|
|||
|
40b8ea8eb8
|
|||
|
f9b4dd45d7
|
|||
|
a46de4662c
|
|||
|
fdd14b860e
|
|||
|
cb62df81e2
|
|||
|
46717e39a7
|
|||
|
344ed6e348
|
|||
|
a8b62fb0eb
|
|||
|
00b3087d6a
|
|||
|
78f3873a0c
|
|||
|
a7f4173df7
|
|||
|
f51c3c1724
|
|||
|
a92dc7e140
|
|||
|
c42befed6b
|
|||
|
2b95d58611
|
|||
|
726a752fbb
|
|||
|
2024972832
|
|||
|
d553ca2ca7
|
|||
|
aeef16495f
|
|||
|
9b26a2a7eb
|
|||
|
2317033dae
|
|||
|
fd6e9c9780
|
|||
|
af0a2ff493
|
|||
|
b142a71c32
|
|||
|
27e3cc853a
|
|||
|
590519c28f
|
|||
|
8ccf8100d4
|
|||
|
ec21a94921
|
|||
|
7b7a6c9218
|
|||
|
0e44d9c514
|
|||
|
e449e16d33
|
|||
|
3ce2b36c15
|
|||
|
f7388822e0
|
|||
|
3800dae8b7
|
|||
|
c62ed191f3
|
|||
|
8b77f0e0ad
|
|||
|
2b56c6f1e5
|
|||
|
ef02265ccd
|
|||
|
f4505d2ecc
|
|||
|
9d2242d331
|
|||
|
c806365a81
|
|||
|
bd1715c9a3
|
|||
|
0b0598712e
|
|||
|
92a4899e7c
|
|||
|
bdc8db3091
|
|||
|
a16da37221
|
|||
|
70a18b07ff
|
|||
|
98b8d5f33b
|
|||
|
2a35786204
|
|||
|
7016a0a943
|
|||
|
cad72502d9
|
|||
|
226a64df41
|
|||
|
75b8567a28
|
|||
|
3aa5561a07
|
|||
|
c0ebb496fe
|
|||
|
afccb27bd4
|
|||
|
6ed96780ab
|
|||
|
8e5cdfbc62
|
|||
|
1b774c1de6
|
|||
|
9b4cbade5c
|
|||
|
a52e54f672
|
|||
|
aa48d5e25d
|
|||
|
ce18b194a5
|
|||
|
382579a20e
|
|||
|
18d50346a9
|
|||
|
ac51bbde6c
|
|||
|
4ab0dcf1c2
|
|||
|
587066d847
|
|||
|
faa375042a
|
|||
|
65b6f3a606
|
4
.github/workflows/docker-build.yml
vendored
4
.github/workflows/docker-build.yml
vendored
@@ -27,8 +27,8 @@ jobs:
|
||||
run: |
|
||||
files="${{ steps.changed-files.outputs.files }}"
|
||||
matrix="{\"include\":[]}"
|
||||
services=("Sphere" "Pass" "Ring" "Drive" "Develop" "Gateway" "Insight")
|
||||
images=("sphere" "pass" "ring" "drive" "develop" "gateway" "insight")
|
||||
services=("Sphere" "Pass" "Ring" "Drive" "Develop" "Gateway" "Insight" "Zone")
|
||||
images=("sphere" "pass" "ring" "drive" "develop" "gateway" "insight" "zone")
|
||||
changed_services=()
|
||||
|
||||
for file in $files; do
|
||||
|
||||
@@ -4,8 +4,8 @@ var builder = DistributedApplication.CreateBuilder(args);
|
||||
|
||||
var isDev = builder.Environment.IsDevelopment();
|
||||
|
||||
var cache = builder.AddRedis("cache");
|
||||
var queue = builder.AddNats("queue").WithJetStream();
|
||||
var cache = builder.AddRedis("Cache");
|
||||
var queue = builder.AddNats("Queue").WithJetStream();
|
||||
|
||||
var ringService = builder.AddProject<Projects.DysonNetwork_Ring>("ring");
|
||||
var passService = builder.AddProject<Projects.DysonNetwork_Pass>("pass")
|
||||
@@ -26,11 +26,17 @@ var insightService = builder.AddProject<Projects.DysonNetwork_Insight>("insight"
|
||||
.WithReference(ringService)
|
||||
.WithReference(sphereService)
|
||||
.WithReference(developService);
|
||||
var zoneService = builder.AddProject<Projects.DysonNetwork_Zone>("zone")
|
||||
.WithReference(passService)
|
||||
.WithReference(ringService)
|
||||
.WithReference(sphereService)
|
||||
.WithReference(developService)
|
||||
.WithReference(insightService);
|
||||
|
||||
passService.WithReference(developService).WithReference(driveService);
|
||||
|
||||
List<IResourceBuilder<ProjectResource>> services =
|
||||
[ringService, passService, driveService, sphereService, developService, insightService];
|
||||
[ringService, passService, driveService, sphereService, developService, insightService, zoneService];
|
||||
|
||||
for (var idx = 0; idx < services.Count; idx++)
|
||||
{
|
||||
|
||||
@@ -1,28 +1,29 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Sdk Name="Aspire.AppHost.Sdk" Version="13.0.0" />
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<UserSecretsId>a68b3195-a00d-40c2-b5ed-d675356b7cde</UserSecretsId>
|
||||
<RootNamespace>DysonNetwork.Control</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Aspire.Hosting.AppHost" Version="13.0.0" />
|
||||
<PackageReference Include="Aspire.Hosting.Docker" Version="13.0.0-preview.1.25560.3" />
|
||||
<PackageReference Include="Aspire.Hosting.Nats" Version="13.0.0" />
|
||||
<PackageReference Include="Aspire.Hosting.Redis" Version="13.0.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\DysonNetwork.Develop\DysonNetwork.Develop.csproj" />
|
||||
<ProjectReference Include="..\DysonNetwork.Drive\DysonNetwork.Drive.csproj" />
|
||||
<ProjectReference Include="..\DysonNetwork.Pass\DysonNetwork.Pass.csproj" />
|
||||
<ProjectReference Include="..\DysonNetwork.Ring\DysonNetwork.Ring.csproj" />
|
||||
<ProjectReference Include="..\DysonNetwork.Sphere\DysonNetwork.Sphere.csproj" />
|
||||
<ProjectReference Include="..\DysonNetwork.Gateway\DysonNetwork.Gateway.csproj" />
|
||||
<ProjectReference Include="..\DysonNetwork.Insight\DysonNetwork.Insight.csproj" />
|
||||
</ItemGroup>
|
||||
<Sdk Name="Aspire.AppHost.Sdk" Version="13.0.0"/>
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<UserSecretsId>a68b3195-a00d-40c2-b5ed-d675356b7cde</UserSecretsId>
|
||||
<RootNamespace>DysonNetwork.Control</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Aspire.Hosting.AppHost" Version="13.0.0"/>
|
||||
<PackageReference Include="Aspire.Hosting.Docker" Version="13.0.0-preview.1.25560.3"/>
|
||||
<PackageReference Include="Aspire.Hosting.Nats" Version="13.0.0"/>
|
||||
<PackageReference Include="Aspire.Hosting.Redis" Version="13.0.0"/>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\DysonNetwork.Develop\DysonNetwork.Develop.csproj"/>
|
||||
<ProjectReference Include="..\DysonNetwork.Drive\DysonNetwork.Drive.csproj"/>
|
||||
<ProjectReference Include="..\DysonNetwork.Pass\DysonNetwork.Pass.csproj"/>
|
||||
<ProjectReference Include="..\DysonNetwork.Ring\DysonNetwork.Ring.csproj"/>
|
||||
<ProjectReference Include="..\DysonNetwork.Sphere\DysonNetwork.Sphere.csproj"/>
|
||||
<ProjectReference Include="..\DysonNetwork.Gateway\DysonNetwork.Gateway.csproj"/>
|
||||
<ProjectReference Include="..\DysonNetwork.Insight\DysonNetwork.Insight.csproj"/>
|
||||
<ProjectReference Include="..\DysonNetwork.Zone\DysonNetwork.Zone.csproj"/>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -69,7 +69,7 @@ public class DeveloperController(
|
||||
|
||||
[HttpPost("{name}/enroll")]
|
||||
[Authorize]
|
||||
[RequiredPermission("global", "developers.create")]
|
||||
[AskPermission("developers.create")]
|
||||
public async Task<ActionResult<SnDeveloper>> EnrollDeveloperProgram(string name)
|
||||
{
|
||||
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized();
|
||||
|
||||
@@ -16,7 +16,7 @@ public static class ApplicationConfiguration
|
||||
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
app.UseMiddleware<PermissionMiddleware>();
|
||||
app.UseMiddleware<RemotePermissionMiddleware>();
|
||||
|
||||
app.MapControllers();
|
||||
|
||||
|
||||
@@ -16,9 +16,7 @@ public static class ServiceCollectionExtensions
|
||||
services.AddLocalization();
|
||||
|
||||
services.AddDbContext<AppDatabase>();
|
||||
services.AddSingleton<IClock>(SystemClock.Instance);
|
||||
services.AddHttpContextAccessor();
|
||||
services.AddSingleton<ICacheService, CacheServiceRedis>();
|
||||
|
||||
services.AddHttpClient();
|
||||
|
||||
|
||||
@@ -1,22 +1,25 @@
|
||||
{
|
||||
"Debug": true,
|
||||
"BaseUrl": "http://localhost:5071",
|
||||
"SiteUrl": "https://solian.app",
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
"Debug": true,
|
||||
"BaseUrl": "http://localhost:5071",
|
||||
"SiteUrl": "https://solian.app",
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*",
|
||||
"ConnectionStrings": {
|
||||
"App": "Host=localhost;Port=5432;Database=dyson_develop;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60"
|
||||
},
|
||||
"KnownProxies": [
|
||||
"127.0.0.1",
|
||||
"::1"
|
||||
],
|
||||
"Swagger": {
|
||||
"PublicBasePath": "/develop"
|
||||
},
|
||||
"Cache": {
|
||||
"Serializer": "MessagePack"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*",
|
||||
"ConnectionStrings": {
|
||||
"App": "Host=localhost;Port=5432;Database=dyson_develop;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60"
|
||||
},
|
||||
"KnownProxies": ["127.0.0.1", "::1"],
|
||||
"Swagger": {
|
||||
"PublicBasePath": "/develop"
|
||||
},
|
||||
"Etcd": {
|
||||
"Insecure": true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ using DysonNetwork.Shared.Data;
|
||||
using DysonNetwork.Shared.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Design;
|
||||
using Microsoft.EntityFrameworkCore.Query;
|
||||
using NodaTime;
|
||||
using Quartz;
|
||||
using TaskStatus = DysonNetwork.Drive.Storage.Model.TaskStatus;
|
||||
@@ -24,7 +23,7 @@ public class AppDatabase(
|
||||
public DbSet<QuotaRecord> QuotaRecords { get; set; } = null!;
|
||||
|
||||
public DbSet<SnCloudFile> Files { get; set; } = null!;
|
||||
public DbSet<CloudFileReference> FileReferences { get; set; } = null!;
|
||||
public DbSet<SnCloudFileReference> FileReferences { get; set; } = null!;
|
||||
public DbSet<SnCloudFileIndex> FileIndexes { get; set; }
|
||||
|
||||
public DbSet<PersistentTask> Tasks { get; set; } = null!;
|
||||
|
||||
@@ -24,9 +24,16 @@ public class FileIndexController(
|
||||
/// </summary>
|
||||
/// <param name="path">The path to browse (defaults to root "/")</param>
|
||||
/// <param name="query">Optional query to filter files by name</param>
|
||||
/// <param name="order">The field to order by (date, size, name - defaults to date)</param>
|
||||
/// <param name="orderDesc">Whether to order in descending order (defaults to true)</param>
|
||||
/// <returns>List of files in the specified path</returns>
|
||||
[HttpGet("browse")]
|
||||
public async Task<IActionResult> BrowseFiles([FromQuery] string path = "/", [FromQuery] string? query = null)
|
||||
public async Task<IActionResult> BrowseFiles(
|
||||
[FromQuery] string path = "/",
|
||||
[FromQuery] string? query = null,
|
||||
[FromQuery] string order = "date",
|
||||
[FromQuery] bool orderDesc = true
|
||||
)
|
||||
{
|
||||
if (HttpContext.Items["CurrentUser"] is not Account currentUser)
|
||||
return new ObjectResult(ApiError.Unauthorized()) { StatusCode = 401 };
|
||||
@@ -44,6 +51,17 @@ public class FileIndexController(
|
||||
.ToList();
|
||||
}
|
||||
|
||||
// Apply sorting
|
||||
fileIndexes = order.ToLower() switch
|
||||
{
|
||||
"name" => orderDesc ? fileIndexes.OrderByDescending(fi => fi.File.Name).ToList()
|
||||
: fileIndexes.OrderBy(fi => fi.File.Name).ToList(),
|
||||
"size" => orderDesc ? fileIndexes.OrderByDescending(fi => fi.File.Size).ToList()
|
||||
: fileIndexes.OrderBy(fi => fi.File.Size).ToList(),
|
||||
_ => orderDesc ? fileIndexes.OrderByDescending(fi => fi.File.CreatedAt).ToList()
|
||||
: fileIndexes.OrderBy(fi => fi.File.CreatedAt).ToList()
|
||||
};
|
||||
|
||||
// Get all file indexes for this account to extract child folders
|
||||
var allFileIndexes = await fileIndexService.GetByAccountIdAsync(accountId);
|
||||
|
||||
@@ -109,9 +127,15 @@ public class FileIndexController(
|
||||
/// Gets all files for the current user (across all paths)
|
||||
/// </summary>
|
||||
/// <param name="query">Optional query to filter files by name</param>
|
||||
/// <param name="order">The field to order by (date, size, name - defaults to date)</param>
|
||||
/// <param name="orderDesc">Whether to order in descending order (defaults to true)</param>
|
||||
/// <returns>List of all files for the user</returns>
|
||||
[HttpGet("all")]
|
||||
public async Task<IActionResult> GetAllFiles([FromQuery] string? query = null)
|
||||
public async Task<IActionResult> GetAllFiles(
|
||||
[FromQuery] string? query = null,
|
||||
[FromQuery] string order = "date",
|
||||
[FromQuery] bool orderDesc = true
|
||||
)
|
||||
{
|
||||
if (HttpContext.Items["CurrentUser"] is not Account currentUser)
|
||||
return new ObjectResult(ApiError.Unauthorized()) { StatusCode = 401 };
|
||||
@@ -129,6 +153,17 @@ public class FileIndexController(
|
||||
.ToList();
|
||||
}
|
||||
|
||||
// Apply sorting
|
||||
fileIndexes = order.ToLower() switch
|
||||
{
|
||||
"name" => orderDesc ? fileIndexes.OrderByDescending(fi => fi.File.Name).ToList()
|
||||
: fileIndexes.OrderBy(fi => fi.File.Name).ToList(),
|
||||
"size" => orderDesc ? fileIndexes.OrderByDescending(fi => fi.File.Size).ToList()
|
||||
: fileIndexes.OrderBy(fi => fi.File.Size).ToList(),
|
||||
_ => orderDesc ? fileIndexes.OrderByDescending(fi => fi.File.CreatedAt).ToList()
|
||||
: fileIndexes.OrderBy(fi => fi.File.CreatedAt).ToList()
|
||||
};
|
||||
|
||||
return Ok(new
|
||||
{
|
||||
Files = fileIndexes,
|
||||
@@ -154,6 +189,9 @@ public class FileIndexController(
|
||||
/// <param name="offset">The number of files to skip</param>
|
||||
/// <param name="take">The number of files to return</param>
|
||||
/// <param name="pool">The pool ID of those files</param>
|
||||
/// <param name="query">Optional query to filter files by name</param>
|
||||
/// <param name="order">The field to order by (date, size, name - defaults to date)</param>
|
||||
/// <param name="orderDesc">Whether to order in descending order (defaults to true)</param>
|
||||
/// <returns>List of unindexed files</returns>
|
||||
[HttpGet("unindexed")]
|
||||
public async Task<IActionResult> GetUnindexedFiles(
|
||||
@@ -161,7 +199,9 @@ public class FileIndexController(
|
||||
[FromQuery] bool recycled = false,
|
||||
[FromQuery] int offset = 0,
|
||||
[FromQuery] int take = 20,
|
||||
[FromQuery] string? query = null
|
||||
[FromQuery] string? query = null,
|
||||
[FromQuery] string order = "date",
|
||||
[FromQuery] bool orderDesc = true
|
||||
)
|
||||
{
|
||||
if (HttpContext.Items["CurrentUser"] is not Account currentUser)
|
||||
@@ -176,9 +216,19 @@ public class FileIndexController(
|
||||
&& f.IsMarkedRecycle == recycled
|
||||
&& !db.FileIndexes.Any(fi => fi.FileId == f.Id && fi.AccountId == accountId)
|
||||
)
|
||||
.OrderByDescending(f => f.CreatedAt)
|
||||
.AsQueryable();
|
||||
|
||||
// Apply sorting
|
||||
filesQuery = order.ToLower() switch
|
||||
{
|
||||
"name" => orderDesc ? filesQuery.OrderByDescending(f => f.Name)
|
||||
: filesQuery.OrderBy(f => f.Name),
|
||||
"size" => orderDesc ? filesQuery.OrderByDescending(f => f.Size)
|
||||
: filesQuery.OrderBy(f => f.Size),
|
||||
_ => orderDesc ? filesQuery.OrderByDescending(f => f.CreatedAt)
|
||||
: filesQuery.OrderBy(f => f.CreatedAt)
|
||||
};
|
||||
|
||||
if (pool.HasValue) filesQuery = filesQuery.Where(f => f.PoolId == pool);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(query))
|
||||
|
||||
@@ -179,7 +179,7 @@ namespace DysonNetwork.Drive.Migrations
|
||||
b.UseTphMappingStrategy();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Shared.Models.CloudFileReference", b =>
|
||||
modelBuilder.Entity("DysonNetwork.Shared.Models.SnCloudFileReference", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
@@ -571,7 +571,7 @@ namespace DysonNetwork.Drive.Migrations
|
||||
b.HasDiscriminator().HasValue("PersistentUploadTask");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Shared.Models.CloudFileReference", b =>
|
||||
modelBuilder.Entity("DysonNetwork.Shared.Models.SnCloudFileReference", b =>
|
||||
{
|
||||
b.HasOne("DysonNetwork.Shared.Models.SnCloudFile", "File")
|
||||
.WithMany("References")
|
||||
|
||||
@@ -3,7 +3,7 @@ using DysonNetwork.Drive.Storage;
|
||||
using DysonNetwork.Drive.Storage.Model;
|
||||
using DysonNetwork.Shared.Models;
|
||||
using DysonNetwork.Shared.Proto;
|
||||
using DysonNetwork.Shared.Stream;
|
||||
using DysonNetwork.Shared.Queue;
|
||||
using FFMpegCore;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NATS.Client.Core;
|
||||
|
||||
@@ -12,9 +12,7 @@ public static class ServiceCollectionExtensions
|
||||
public static IServiceCollection AddAppServices(this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
services.AddDbContext<AppDatabase>(); // Assuming you'll have an AppDatabase
|
||||
services.AddSingleton<IClock>(SystemClock.Instance);
|
||||
services.AddHttpContextAccessor();
|
||||
services.AddSingleton<ICacheService, CacheServiceRedis>(); // Uncomment if you have CacheServiceRedis
|
||||
|
||||
services.AddHttpClient();
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using DysonNetwork.Drive.Billing;
|
||||
using DysonNetwork.Shared.Auth;
|
||||
using DysonNetwork.Shared.Models;
|
||||
using DysonNetwork.Shared.Proto;
|
||||
@@ -14,7 +13,6 @@ namespace DysonNetwork.Drive.Storage;
|
||||
public class FileController(
|
||||
AppDatabase db,
|
||||
FileService fs,
|
||||
QuotaService qs,
|
||||
IConfiguration configuration,
|
||||
IWebHostEnvironment env,
|
||||
FileReferenceService fileReferenceService
|
||||
@@ -233,7 +231,7 @@ public class FileController(
|
||||
}
|
||||
|
||||
[HttpGet("{id}/references")]
|
||||
public async Task<ActionResult<List<Shared.Models.CloudFileReference>>> GetFileReferences(string id)
|
||||
public async Task<ActionResult<List<Shared.Models.SnCloudFileReference>>> GetFileReferences(string id)
|
||||
{
|
||||
var file = await fs.GetFileAsync(id);
|
||||
if (file is null) return NotFound("File not found.");
|
||||
@@ -295,7 +293,9 @@ public class FileController(
|
||||
[FromQuery] bool recycled = false,
|
||||
[FromQuery] int offset = 0,
|
||||
[FromQuery] int take = 20,
|
||||
[FromQuery] string? query = null
|
||||
[FromQuery] string? query = null,
|
||||
[FromQuery] string order = "date",
|
||||
[FromQuery] bool orderDesc = true
|
||||
)
|
||||
{
|
||||
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized();
|
||||
@@ -305,7 +305,6 @@ public class FileController(
|
||||
.Where(e => e.IsMarkedRecycle == recycled)
|
||||
.Where(e => e.AccountId == accountId)
|
||||
.Include(e => e.Pool)
|
||||
.OrderByDescending(e => e.CreatedAt)
|
||||
.AsQueryable();
|
||||
|
||||
if (pool.HasValue) filesQuery = filesQuery.Where(e => e.PoolId == pool);
|
||||
@@ -315,6 +314,14 @@ public class FileController(
|
||||
filesQuery = filesQuery.Where(e => e.Name.Contains(query));
|
||||
}
|
||||
|
||||
filesQuery = order.ToLower() switch
|
||||
{
|
||||
"date" => orderDesc ? filesQuery.OrderByDescending(e => e.CreatedAt) : filesQuery.OrderBy(e => e.CreatedAt),
|
||||
"size" => orderDesc ? filesQuery.OrderByDescending(e => e.Size) : filesQuery.OrderBy(e => e.Size),
|
||||
"name" => orderDesc ? filesQuery.OrderByDescending(e => e.Name) : filesQuery.OrderBy(e => e.Name),
|
||||
_ => filesQuery.OrderByDescending(e => e.CreatedAt)
|
||||
};
|
||||
|
||||
var total = await filesQuery.CountAsync();
|
||||
Response.Headers.Append("X-Total", total.ToString());
|
||||
|
||||
@@ -326,6 +333,22 @@ public class FileController(
|
||||
return Ok(files);
|
||||
}
|
||||
|
||||
public class FileBatchDeletionRequest
|
||||
{
|
||||
public List<string> FileIds { get; set; } = [];
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
[HttpPost("batches/delete")]
|
||||
public async Task<ActionResult> DeleteFileBatch([FromBody] FileBatchDeletionRequest request)
|
||||
{
|
||||
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized();
|
||||
var userId = Guid.Parse(currentUser.Id);
|
||||
|
||||
var count = await fs.DeleteAccountFileBatchAsync(userId, request.FileIds);
|
||||
return Ok(new { Count = count });
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
[HttpDelete("{id}")]
|
||||
public async Task<ActionResult<SnCloudFile>> DeleteFile(string id)
|
||||
@@ -358,10 +381,10 @@ public class FileController(
|
||||
|
||||
[Authorize]
|
||||
[HttpDelete("recycle")]
|
||||
[RequiredPermission("maintenance", "files.delete.recycle")]
|
||||
[AskPermission("files.delete.recycle")]
|
||||
public async Task<ActionResult> DeleteAllRecycledFiles()
|
||||
{
|
||||
var count = await fs.DeleteAllRecycledFilesAsync();
|
||||
return Ok(new { Count = count });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,7 @@ public class FileReferenceService(AppDatabase db, FileService fileService, ICach
|
||||
/// <param name="expiredAt">Optional expiration time for the file</param>
|
||||
/// <param name="duration">Optional duration after which the file expires (alternative to expiredAt)</param>
|
||||
/// <returns>The created file reference</returns>
|
||||
public async Task<CloudFileReference> CreateReferenceAsync(
|
||||
public async Task<SnCloudFileReference> CreateReferenceAsync(
|
||||
string fileId,
|
||||
string usage,
|
||||
string resourceId,
|
||||
@@ -34,7 +34,7 @@ public class FileReferenceService(AppDatabase db, FileService fileService, ICach
|
||||
if (duration.HasValue)
|
||||
finalExpiration = SystemClock.Instance.GetCurrentInstant() + duration.Value;
|
||||
|
||||
var reference = new CloudFileReference
|
||||
var reference = new SnCloudFileReference
|
||||
{
|
||||
FileId = fileId,
|
||||
Usage = usage,
|
||||
@@ -50,7 +50,7 @@ public class FileReferenceService(AppDatabase db, FileService fileService, ICach
|
||||
return reference;
|
||||
}
|
||||
|
||||
public async Task<List<CloudFileReference>> CreateReferencesAsync(
|
||||
public async Task<List<SnCloudFileReference>> CreateReferencesAsync(
|
||||
List<string> fileId,
|
||||
string usage,
|
||||
string resourceId,
|
||||
@@ -58,12 +58,15 @@ public class FileReferenceService(AppDatabase db, FileService fileService, ICach
|
||||
Duration? duration = null
|
||||
)
|
||||
{
|
||||
var data = fileId.Select(id => new CloudFileReference
|
||||
var now = SystemClock.Instance.GetCurrentInstant();
|
||||
var data = fileId.Select(id => new SnCloudFileReference
|
||||
{
|
||||
FileId = id,
|
||||
Usage = usage,
|
||||
ResourceId = resourceId,
|
||||
ExpiredAt = expiredAt ?? SystemClock.Instance.GetCurrentInstant() + duration
|
||||
ExpiredAt = expiredAt ?? now + duration,
|
||||
CreatedAt = now,
|
||||
UpdatedAt = now
|
||||
}).ToList();
|
||||
await db.BulkInsertAsync(data);
|
||||
return data;
|
||||
@@ -74,11 +77,11 @@ public class FileReferenceService(AppDatabase db, FileService fileService, ICach
|
||||
/// </summary>
|
||||
/// <param name="fileId">The ID of the file</param>
|
||||
/// <returns>A list of all references to the file</returns>
|
||||
public async Task<List<CloudFileReference>> GetReferencesAsync(string fileId)
|
||||
public async Task<List<SnCloudFileReference>> GetReferencesAsync(string fileId)
|
||||
{
|
||||
var cacheKey = $"{CacheKeyPrefix}list:{fileId}";
|
||||
|
||||
var cachedReferences = await cache.GetAsync<List<CloudFileReference>>(cacheKey);
|
||||
var cachedReferences = await cache.GetAsync<List<SnCloudFileReference>>(cacheKey);
|
||||
if (cachedReferences is not null)
|
||||
return cachedReferences;
|
||||
|
||||
@@ -91,17 +94,17 @@ public class FileReferenceService(AppDatabase db, FileService fileService, ICach
|
||||
return references;
|
||||
}
|
||||
|
||||
public async Task<Dictionary<string, List<CloudFileReference>>> GetReferencesAsync(IEnumerable<string> fileIds)
|
||||
public async Task<Dictionary<string, List<SnCloudFileReference>>> GetReferencesAsync(IEnumerable<string> fileIds)
|
||||
{
|
||||
var fileIdList = fileIds.ToList();
|
||||
var result = new Dictionary<string, List<CloudFileReference>>();
|
||||
var result = new Dictionary<string, List<SnCloudFileReference>>();
|
||||
|
||||
// Check cache for each file ID
|
||||
var uncachedFileIds = new List<string>();
|
||||
foreach (var fileId in fileIdList)
|
||||
{
|
||||
var cacheKey = $"{CacheKeyPrefix}list:{fileId}";
|
||||
var cachedReferences = await cache.GetAsync<List<CloudFileReference>>(cacheKey);
|
||||
var cachedReferences = await cache.GetAsync<List<SnCloudFileReference>>(cacheKey);
|
||||
if (cachedReferences is not null)
|
||||
{
|
||||
result[fileId] = cachedReferences;
|
||||
@@ -159,11 +162,11 @@ public class FileReferenceService(AppDatabase db, FileService fileService, ICach
|
||||
/// </summary>
|
||||
/// <param name="resourceId">The ID of the resource</param>
|
||||
/// <returns>A list of file references associated with the resource</returns>
|
||||
public async Task<List<CloudFileReference>> GetResourceReferencesAsync(string resourceId)
|
||||
public async Task<List<SnCloudFileReference>> GetResourceReferencesAsync(string resourceId)
|
||||
{
|
||||
var cacheKey = $"{CacheKeyPrefix}resource:{resourceId}";
|
||||
|
||||
var cachedReferences = await cache.GetAsync<List<CloudFileReference>>(cacheKey);
|
||||
var cachedReferences = await cache.GetAsync<List<SnCloudFileReference>>(cacheKey);
|
||||
if (cachedReferences is not null)
|
||||
return cachedReferences;
|
||||
|
||||
@@ -181,11 +184,11 @@ public class FileReferenceService(AppDatabase db, FileService fileService, ICach
|
||||
/// </summary>
|
||||
/// <param name="usage">The usage context</param>
|
||||
/// <returns>A list of file references with the specified usage</returns>
|
||||
public async Task<List<CloudFileReference>> GetUsageReferencesAsync(string usage)
|
||||
public async Task<List<SnCloudFileReference>> GetUsageReferencesAsync(string usage)
|
||||
{
|
||||
var cacheKey = $"{CacheKeyPrefix}usage:{usage}";
|
||||
|
||||
var cachedReferences = await cache.GetAsync<List<CloudFileReference>>(cacheKey);
|
||||
var cachedReferences = await cache.GetAsync<List<SnCloudFileReference>>(cacheKey);
|
||||
if (cachedReferences is not null)
|
||||
return cachedReferences;
|
||||
|
||||
@@ -307,7 +310,7 @@ public class FileReferenceService(AppDatabase db, FileService fileService, ICach
|
||||
/// <param name="expiredAt">Optional expiration time for newly added files</param>
|
||||
/// <param name="duration">Optional duration after which newly added files expire</param>
|
||||
/// <returns>A list of the updated file references</returns>
|
||||
public async Task<List<CloudFileReference>> UpdateResourceFilesAsync(
|
||||
public async Task<List<SnCloudFileReference>> UpdateResourceFilesAsync(
|
||||
string resourceId,
|
||||
IEnumerable<string>? newFileIds,
|
||||
string usage,
|
||||
@@ -315,7 +318,7 @@ public class FileReferenceService(AppDatabase db, FileService fileService, ICach
|
||||
Duration? duration = null)
|
||||
{
|
||||
if (newFileIds == null)
|
||||
return new List<CloudFileReference>();
|
||||
return new List<SnCloudFileReference>();
|
||||
|
||||
var existingReferences = await db.FileReferences
|
||||
.Where(r => r.ResourceId == resourceId && r.Usage == usage)
|
||||
@@ -333,7 +336,7 @@ public class FileReferenceService(AppDatabase db, FileService fileService, ICach
|
||||
// Files to add
|
||||
var toAdd = newFileIdsList
|
||||
.Where(id => !existingFileIds.Contains(id))
|
||||
.Select(id => new CloudFileReference
|
||||
.Select(id => new SnCloudFileReference
|
||||
{
|
||||
FileId = id,
|
||||
Usage = usage,
|
||||
@@ -485,7 +488,7 @@ public class FileReferenceService(AppDatabase db, FileService fileService, ICach
|
||||
/// <param name="resourceId">The resource ID</param>
|
||||
/// <param name="usageType">The usage type</param>
|
||||
/// <returns>List of file references</returns>
|
||||
public async Task<List<CloudFileReference>> GetResourceReferencesAsync(string resourceId, string usageType)
|
||||
public async Task<List<SnCloudFileReference>> GetResourceReferencesAsync(string resourceId, string usageType)
|
||||
{
|
||||
return await db.FileReferences
|
||||
.Where(r => r.ResourceId == resourceId && r.Usage == usageType)
|
||||
|
||||
@@ -718,6 +718,21 @@ public class FileService(
|
||||
return count;
|
||||
}
|
||||
|
||||
public async Task<int> DeleteAccountFileBatchAsync(Guid accountId, List<string> fileIds)
|
||||
{
|
||||
var files = await db.Files
|
||||
.Where(f => f.AccountId == accountId && fileIds.Contains(f.Id))
|
||||
.ToListAsync();
|
||||
var count = files.Count;
|
||||
var tasks = files.Select(f => DeleteFileDataAsync(f, true));
|
||||
await Task.WhenAll(tasks);
|
||||
var fileIdsList = files.Select(f => f.Id).ToList();
|
||||
await _PurgeCacheRangeAsync(fileIdsList);
|
||||
db.RemoveRange(files);
|
||||
await db.SaveChangesAsync();
|
||||
return count;
|
||||
}
|
||||
|
||||
public async Task<int> DeletePoolRecycledFilesAsync(Guid poolId)
|
||||
{
|
||||
var files = await db.Files
|
||||
@@ -788,4 +803,4 @@ file class UpdatableCloudFile(SnCloudFile file)
|
||||
.SetProperty(f => f.UserMeta, userMeta)
|
||||
.SetProperty(f => f.IsMarkedRecycle, IsMarkedRecycle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,7 +113,7 @@ public class FileUploadController(
|
||||
if (currentUser.IsSuperuser) return null;
|
||||
|
||||
var allowed = await permission.HasPermissionAsync(new HasPermissionRequest
|
||||
{ Actor = $"user:{currentUser.Id}", Area = "global", Key = "files.create" });
|
||||
{ Actor = currentUser.Id, Key = "files.create" });
|
||||
|
||||
return allowed.HasPermission
|
||||
? null
|
||||
|
||||
@@ -1,118 +1,121 @@
|
||||
{
|
||||
"Debug": true,
|
||||
"BaseUrl": "http://localhost:5090",
|
||||
"GatewayUrl": "http://localhost:5094",
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*",
|
||||
"ConnectionStrings": {
|
||||
"App": "Host=localhost;Port=5432;Database=dyson_drive;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60"
|
||||
},
|
||||
"Authentication": {
|
||||
"Schemes": {
|
||||
"Bearer": {
|
||||
"ValidAudiences": [
|
||||
"http://localhost:5071",
|
||||
"https://localhost:7099"
|
||||
],
|
||||
"ValidIssuer": "solar-network"
|
||||
}
|
||||
}
|
||||
},
|
||||
"AuthToken": {
|
||||
"PublicKeyPath": "Keys/PublicKey.pem",
|
||||
"PrivateKeyPath": "Keys/PrivateKey.pem"
|
||||
},
|
||||
"Storage": {
|
||||
"Uploads": "Uploads",
|
||||
"PreferredRemote": "c53136a6-9152-4ecb-9f88-43c41438c23e",
|
||||
"Remote": [
|
||||
{
|
||||
"Id": "minio",
|
||||
"Label": "Minio",
|
||||
"Region": "auto",
|
||||
"Bucket": "solar-network-development",
|
||||
"Endpoint": "localhost:9000",
|
||||
"SecretId": "littlesheep",
|
||||
"SecretKey": "password",
|
||||
"EnabledSigned": true,
|
||||
"EnableSsl": false
|
||||
},
|
||||
{
|
||||
"Id": "cloudflare",
|
||||
"Label": "Cloudflare R2",
|
||||
"Region": "auto",
|
||||
"Bucket": "solar-network",
|
||||
"Endpoint": "0a70a6d1b7128888c823359d0008f4e1.r2.cloudflarestorage.com",
|
||||
"SecretId": "8ff5d06c7b1639829d60bc6838a542e6",
|
||||
"SecretKey": "fd58158c5201be16d1872c9209d9cf199421dae3c2f9972f94b2305976580d67",
|
||||
"EnableSigned": true,
|
||||
"EnableSsl": true
|
||||
}
|
||||
"Debug": true,
|
||||
"BaseUrl": "http://localhost:5090",
|
||||
"GatewayUrl": "http://localhost:5094",
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*",
|
||||
"ConnectionStrings": {
|
||||
"App": "Host=localhost;Port=5432;Database=dyson_drive;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60"
|
||||
},
|
||||
"Authentication": {
|
||||
"Schemes": {
|
||||
"Bearer": {
|
||||
"ValidAudiences": [
|
||||
"http://localhost:5071",
|
||||
"https://localhost:7099"
|
||||
],
|
||||
"ValidIssuer": "solar-network"
|
||||
}
|
||||
}
|
||||
},
|
||||
"AuthToken": {
|
||||
"PublicKeyPath": "Keys/PublicKey.pem",
|
||||
"PrivateKeyPath": "Keys/PrivateKey.pem"
|
||||
},
|
||||
"Storage": {
|
||||
"Uploads": "Uploads",
|
||||
"PreferredRemote": "c53136a6-9152-4ecb-9f88-43c41438c23e",
|
||||
"Remote": [
|
||||
{
|
||||
"Id": "minio",
|
||||
"Label": "Minio",
|
||||
"Region": "auto",
|
||||
"Bucket": "solar-network-development",
|
||||
"Endpoint": "localhost:9000",
|
||||
"SecretId": "littlesheep",
|
||||
"SecretKey": "password",
|
||||
"EnabledSigned": true,
|
||||
"EnableSsl": false
|
||||
},
|
||||
{
|
||||
"Id": "cloudflare",
|
||||
"Label": "Cloudflare R2",
|
||||
"Region": "auto",
|
||||
"Bucket": "solar-network",
|
||||
"Endpoint": "0a70a6d1b7128888c823359d0008f4e1.r2.cloudflarestorage.com",
|
||||
"SecretId": "8ff5d06c7b1639829d60bc6838a542e6",
|
||||
"SecretKey": "fd58158c5201be16d1872c9209d9cf199421dae3c2f9972f94b2305976580d67",
|
||||
"EnableSigned": true,
|
||||
"EnableSsl": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"Captcha": {
|
||||
"Provider": "cloudflare",
|
||||
"ApiKey": "0x4AAAAAABCDUdOujj4feOb_",
|
||||
"ApiSecret": "0x4AAAAAABCDUWABiJQweqlB7tYq-IqIm8U"
|
||||
},
|
||||
"Notifications": {
|
||||
"Topic": "dev.solsynth.solian",
|
||||
"Endpoint": "http://localhost:8088"
|
||||
},
|
||||
"Email": {
|
||||
"Server": "smtp4dev.orb.local",
|
||||
"Port": 25,
|
||||
"UseSsl": false,
|
||||
"Username": "no-reply@mail.solsynth.dev",
|
||||
"Password": "password",
|
||||
"FromAddress": "no-reply@mail.solsynth.dev",
|
||||
"FromName": "Alphabot",
|
||||
"SubjectPrefix": "Solar Network"
|
||||
},
|
||||
"RealtimeChat": {
|
||||
"Endpoint": "https://solar-network-im44o8gq.livekit.cloud",
|
||||
"ApiKey": "APIs6TiL8wj3A4j",
|
||||
"ApiSecret": "SffxRneIwTnlHPtEf3zicmmv3LUEl7xXael4PvWZrEhE"
|
||||
},
|
||||
"GeoIp": {
|
||||
"DatabasePath": "./Keys/GeoLite2-City.mmdb"
|
||||
},
|
||||
"Oidc": {
|
||||
"Google": {
|
||||
"ClientId": "961776991058-963m1qin2vtp8fv693b5fdrab5hmpl89.apps.googleusercontent.com",
|
||||
"ClientSecret": ""
|
||||
},
|
||||
"Apple": {
|
||||
"ClientId": "dev.solsynth.solian",
|
||||
"TeamId": "W7HPZ53V6B",
|
||||
"KeyId": "B668YP4KBG",
|
||||
"PrivateKeyPath": "./Keys/Solarpass.p8"
|
||||
},
|
||||
"Microsoft": {
|
||||
"ClientId": "YOUR_MICROSOFT_CLIENT_ID",
|
||||
"ClientSecret": "YOUR_MICROSOFT_CLIENT_SECRET",
|
||||
"DiscoveryEndpoint": "YOUR_MICROSOFT_DISCOVERY_ENDPOINT"
|
||||
}
|
||||
},
|
||||
"Payment": {
|
||||
"Auth": {
|
||||
"Afdian": "<token here>"
|
||||
},
|
||||
"Subscriptions": {
|
||||
"Afdian": {
|
||||
"7d17aae23c9611f0b5705254001e7c00": "solian.stellar.primary",
|
||||
"7dfae4743c9611f0b3a55254001e7c00": "solian.stellar.nova",
|
||||
"141713ee3d6211f085b352540025c377": "solian.stellar.supernova"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Cache": {
|
||||
"Serializer": "MessagePack"
|
||||
},
|
||||
"KnownProxies": [
|
||||
"127.0.0.1",
|
||||
"::1"
|
||||
]
|
||||
},
|
||||
"Captcha": {
|
||||
"Provider": "cloudflare",
|
||||
"ApiKey": "0x4AAAAAABCDUdOujj4feOb_",
|
||||
"ApiSecret": "0x4AAAAAABCDUWABiJQweqlB7tYq-IqIm8U"
|
||||
},
|
||||
"Notifications": {
|
||||
"Topic": "dev.solsynth.solian",
|
||||
"Endpoint": "http://localhost:8088"
|
||||
},
|
||||
"Email": {
|
||||
"Server": "smtp4dev.orb.local",
|
||||
"Port": 25,
|
||||
"UseSsl": false,
|
||||
"Username": "no-reply@mail.solsynth.dev",
|
||||
"Password": "password",
|
||||
"FromAddress": "no-reply@mail.solsynth.dev",
|
||||
"FromName": "Alphabot",
|
||||
"SubjectPrefix": "Solar Network"
|
||||
},
|
||||
"RealtimeChat": {
|
||||
"Endpoint": "https://solar-network-im44o8gq.livekit.cloud",
|
||||
"ApiKey": "APIs6TiL8wj3A4j",
|
||||
"ApiSecret": "SffxRneIwTnlHPtEf3zicmmv3LUEl7xXael4PvWZrEhE"
|
||||
},
|
||||
"GeoIp": {
|
||||
"DatabasePath": "./Keys/GeoLite2-City.mmdb"
|
||||
},
|
||||
"Oidc": {
|
||||
"Google": {
|
||||
"ClientId": "961776991058-963m1qin2vtp8fv693b5fdrab5hmpl89.apps.googleusercontent.com",
|
||||
"ClientSecret": ""
|
||||
},
|
||||
"Apple": {
|
||||
"ClientId": "dev.solsynth.solian",
|
||||
"TeamId": "W7HPZ53V6B",
|
||||
"KeyId": "B668YP4KBG",
|
||||
"PrivateKeyPath": "./Keys/Solarpass.p8"
|
||||
},
|
||||
"Microsoft": {
|
||||
"ClientId": "YOUR_MICROSOFT_CLIENT_ID",
|
||||
"ClientSecret": "YOUR_MICROSOFT_CLIENT_SECRET",
|
||||
"DiscoveryEndpoint": "YOUR_MICROSOFT_DISCOVERY_ENDPOINT"
|
||||
}
|
||||
},
|
||||
"Payment": {
|
||||
"Auth": {
|
||||
"Afdian": "<token here>"
|
||||
},
|
||||
"Subscriptions": {
|
||||
"Afdian": {
|
||||
"7d17aae23c9611f0b5705254001e7c00": "solian.stellar.primary",
|
||||
"7dfae4743c9611f0b3a55254001e7c00": "solian.stellar.nova",
|
||||
"141713ee3d6211f085b352540025c377": "solian.stellar.supernova"
|
||||
}
|
||||
}
|
||||
},
|
||||
"KnownProxies": [
|
||||
"127.0.0.1",
|
||||
"::1"
|
||||
]
|
||||
}
|
||||
|
||||
9
DysonNetwork.Gateway/Health/GatewayConstant.cs
Normal file
9
DysonNetwork.Gateway/Health/GatewayConstant.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace DysonNetwork.Gateway.Health;
|
||||
|
||||
public abstract class GatewayConstant
|
||||
{
|
||||
public static readonly string[] ServiceNames = ["ring", "pass", "drive", "sphere", "develop", "insight", "zone"];
|
||||
|
||||
// Core services stands with w/o these services the functional of entire app will broke.
|
||||
public static readonly string[] CoreServiceNames = ["ring", "pass", "drive", "sphere"];
|
||||
}
|
||||
60
DysonNetwork.Gateway/Health/GatewayHealthAggregator.cs
Normal file
60
DysonNetwork.Gateway/Health/GatewayHealthAggregator.cs
Normal file
@@ -0,0 +1,60 @@
|
||||
using NodaTime;
|
||||
|
||||
namespace DysonNetwork.Gateway.Health;
|
||||
|
||||
public class GatewayHealthAggregator(IHttpClientFactory httpClientFactory, GatewayReadinessStore store)
|
||||
: BackgroundService
|
||||
{
|
||||
private async Task<ServiceHealthState> CheckService(string serviceName)
|
||||
{
|
||||
var client = httpClientFactory.CreateClient("health");
|
||||
var now = SystemClock.Instance.GetCurrentInstant();
|
||||
|
||||
try
|
||||
{
|
||||
// Use the service discovery to lookup service
|
||||
// The service defaults give every single service a health endpoint that we can use here
|
||||
using var response = await client.GetAsync($"http://{serviceName}/health");
|
||||
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
return new ServiceHealthState(
|
||||
serviceName,
|
||||
true,
|
||||
now,
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
return new ServiceHealthState(
|
||||
serviceName,
|
||||
false,
|
||||
now,
|
||||
$"StatusCode: {(int)response.StatusCode}"
|
||||
);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new ServiceHealthState(
|
||||
serviceName,
|
||||
false,
|
||||
now,
|
||||
ex.Message
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
while (!stoppingToken.IsCancellationRequested)
|
||||
{
|
||||
foreach (var service in GatewayConstant.ServiceNames)
|
||||
{
|
||||
var result = await CheckService(service);
|
||||
store.Update(result);
|
||||
}
|
||||
|
||||
await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
35
DysonNetwork.Gateway/Health/GatewayReadinessMiddleware.cs
Normal file
35
DysonNetwork.Gateway/Health/GatewayReadinessMiddleware.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
namespace DysonNetwork.Gateway.Health;
|
||||
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
public sealed class GatewayReadinessMiddleware(RequestDelegate next)
|
||||
{
|
||||
public async Task InvokeAsync(HttpContext context, GatewayReadinessStore store)
|
||||
{
|
||||
if (context.Request.Path.StartsWithSegments("/health"))
|
||||
{
|
||||
await next(context);
|
||||
return;
|
||||
}
|
||||
|
||||
var readiness = store.Current;
|
||||
|
||||
// Only core services participate in readiness gating
|
||||
var notReadyCoreServices = readiness.Services
|
||||
.Where(kv => GatewayConstant.CoreServiceNames.Contains(kv.Key))
|
||||
.Where(kv => !kv.Value.IsHealthy)
|
||||
.Select(kv => kv.Key)
|
||||
.ToArray();
|
||||
|
||||
if (notReadyCoreServices.Length > 0)
|
||||
{
|
||||
context.Response.StatusCode = StatusCodes.Status503ServiceUnavailable;
|
||||
var unavailableServices = string.Join(", ", notReadyCoreServices);
|
||||
context.Response.Headers["X-NotReady"] = unavailableServices;
|
||||
await context.Response.WriteAsync("Solar Network is warming up. Try again later please.");
|
||||
return;
|
||||
}
|
||||
|
||||
await next(context);
|
||||
}
|
||||
}
|
||||
76
DysonNetwork.Gateway/Health/GatewayReadinessStore.cs
Normal file
76
DysonNetwork.Gateway/Health/GatewayReadinessStore.cs
Normal file
@@ -0,0 +1,76 @@
|
||||
using NodaTime;
|
||||
|
||||
namespace DysonNetwork.Gateway.Health;
|
||||
|
||||
public record ServiceHealthState(
|
||||
string ServiceName,
|
||||
bool IsHealthy,
|
||||
Instant LastChecked,
|
||||
string? Error
|
||||
);
|
||||
|
||||
public record GatewayReadinessState(
|
||||
bool IsReady,
|
||||
IReadOnlyDictionary<string, ServiceHealthState> Services,
|
||||
Instant LastUpdated
|
||||
);
|
||||
|
||||
public class GatewayReadinessStore
|
||||
{
|
||||
private readonly Lock _lock = new();
|
||||
|
||||
private readonly Dictionary<string, ServiceHealthState> _services = new();
|
||||
|
||||
public GatewayReadinessState Current { get; private set; } = new(
|
||||
IsReady: false,
|
||||
Services: new Dictionary<string, ServiceHealthState>(),
|
||||
LastUpdated: SystemClock.Instance.GetCurrentInstant()
|
||||
);
|
||||
|
||||
public IReadOnlyCollection<string> ServiceNames => _services.Keys;
|
||||
|
||||
public GatewayReadinessStore()
|
||||
{
|
||||
InitializeServices(GatewayConstant.ServiceNames);
|
||||
}
|
||||
|
||||
private void InitializeServices(IEnumerable<string> serviceNames)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_services.Clear();
|
||||
|
||||
foreach (var name in serviceNames)
|
||||
{
|
||||
_services[name] = new ServiceHealthState(
|
||||
name,
|
||||
IsHealthy: false,
|
||||
LastChecked: SystemClock.Instance.GetCurrentInstant(),
|
||||
Error: "Not checked yet"
|
||||
);
|
||||
}
|
||||
|
||||
RecalculateLocked();
|
||||
}
|
||||
}
|
||||
|
||||
public void Update(ServiceHealthState state)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_services[state.ServiceName] = state;
|
||||
RecalculateLocked();
|
||||
}
|
||||
}
|
||||
|
||||
private void RecalculateLocked()
|
||||
{
|
||||
var isReady = _services.Count > 0 && _services.Values.All(s => s.IsHealthy);
|
||||
|
||||
Current = new GatewayReadinessState(
|
||||
IsReady: isReady,
|
||||
Services: new Dictionary<string, ServiceHealthState>(_services),
|
||||
LastUpdated: SystemClock.Instance.GetCurrentInstant()
|
||||
);
|
||||
}
|
||||
}
|
||||
14
DysonNetwork.Gateway/Health/GatewayStatusController.cs
Normal file
14
DysonNetwork.Gateway/Health/GatewayStatusController.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace DysonNetwork.Gateway.Health;
|
||||
|
||||
[ApiController]
|
||||
[Route("/health")]
|
||||
public class GatewayStatusController(GatewayReadinessStore readinessStore) : ControllerBase
|
||||
{
|
||||
[HttpGet]
|
||||
public ActionResult<GatewayReadinessState> GetHealthStatus()
|
||||
{
|
||||
return Ok(readinessStore.Current);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,12 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading.RateLimiting;
|
||||
using DysonNetwork.Gateway.Health;
|
||||
using DysonNetwork.Shared.Http;
|
||||
using Yarp.ReverseProxy.Configuration;
|
||||
using Microsoft.AspNetCore.HttpOverrides;
|
||||
using NodaTime;
|
||||
using NodaTime.Serialization.SystemTextJson;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
@@ -9,17 +14,19 @@ builder.AddServiceDefaults();
|
||||
|
||||
builder.ConfigureAppKestrel(builder.Configuration, maxRequestBodySize: long.MaxValue, enableGrpc: false);
|
||||
|
||||
builder.Services.AddSingleton<GatewayReadinessStore>();
|
||||
builder.Services.AddHostedService<GatewayHealthAggregator>();
|
||||
|
||||
builder.Services.AddCors(options =>
|
||||
{
|
||||
options.AddDefaultPolicy(
|
||||
policy =>
|
||||
{
|
||||
policy.SetIsOriginAllowed(origin => true)
|
||||
.AllowAnyMethod()
|
||||
.AllowAnyHeader()
|
||||
.AllowCredentials()
|
||||
.WithExposedHeaders("X-Total");
|
||||
});
|
||||
options.AddDefaultPolicy(policy =>
|
||||
{
|
||||
policy.SetIsOriginAllowed(origin => true)
|
||||
.AllowAnyMethod()
|
||||
.AllowAnyHeader()
|
||||
.AllowCredentials()
|
||||
.WithExposedHeaders("X-Total", "X-NotReady");
|
||||
});
|
||||
});
|
||||
|
||||
builder.Services.AddRateLimiter(options =>
|
||||
@@ -40,23 +47,22 @@ builder.Services.AddRateLimiter(options =>
|
||||
});
|
||||
|
||||
options.OnRejected = async (context, token) =>
|
||||
{
|
||||
// Log the rejected IP
|
||||
var logger = context.HttpContext.RequestServices
|
||||
.GetRequiredService<ILoggerFactory>()
|
||||
.CreateLogger("RateLimiter");
|
||||
{
|
||||
// Log the rejected IP
|
||||
var logger = context.HttpContext.RequestServices
|
||||
.GetRequiredService<ILoggerFactory>()
|
||||
.CreateLogger("RateLimiter");
|
||||
|
||||
var ip = context.HttpContext.Connection.RemoteIpAddress?.ToString() ?? "unknown";
|
||||
logger.LogWarning("Rate limit exceeded for IP: {IP}", ip);
|
||||
var ip = context.HttpContext.Connection.RemoteIpAddress?.ToString() ?? "unknown";
|
||||
logger.LogWarning("Rate limit exceeded for IP: {IP}", ip);
|
||||
|
||||
// Respond to the client
|
||||
context.HttpContext.Response.StatusCode = StatusCodes.Status429TooManyRequests;
|
||||
await context.HttpContext.Response.WriteAsync(
|
||||
"Rate limit exceeded. Try again later.", token);
|
||||
};
|
||||
// Respond to the client
|
||||
context.HttpContext.Response.StatusCode = StatusCodes.Status429TooManyRequests;
|
||||
await context.HttpContext.Response.WriteAsync(
|
||||
"Rate limit exceeded. Try again later.", token);
|
||||
};
|
||||
});
|
||||
|
||||
var serviceNames = new[] { "ring", "pass", "drive", "sphere", "develop", "insight" };
|
||||
|
||||
var specialRoutes = new[]
|
||||
{
|
||||
@@ -86,7 +92,7 @@ var specialRoutes = new[]
|
||||
}
|
||||
};
|
||||
|
||||
var apiRoutes = serviceNames.Select(serviceName =>
|
||||
var apiRoutes = GatewayConstant.ServiceNames.Select(serviceName =>
|
||||
{
|
||||
var apiPath = serviceName switch
|
||||
{
|
||||
@@ -105,7 +111,7 @@ var apiRoutes = serviceNames.Select(serviceName =>
|
||||
};
|
||||
});
|
||||
|
||||
var swaggerRoutes = serviceNames.Select(serviceName => new RouteConfig
|
||||
var swaggerRoutes = GatewayConstant.ServiceNames.Select(serviceName => new RouteConfig
|
||||
{
|
||||
RouteId = $"{serviceName}-swagger",
|
||||
ClusterId = serviceName,
|
||||
@@ -119,7 +125,7 @@ var swaggerRoutes = serviceNames.Select(serviceName => new RouteConfig
|
||||
|
||||
var routes = specialRoutes.Concat(apiRoutes).Concat(swaggerRoutes).ToArray();
|
||||
|
||||
var clusters = serviceNames.Select(serviceName => new ClusterConfig
|
||||
var clusters = GatewayConstant.ServiceNames.Select(serviceName => new ClusterConfig
|
||||
{
|
||||
ClusterId = serviceName,
|
||||
HealthCheck = new HealthCheckConfig
|
||||
@@ -131,7 +137,7 @@ var clusters = serviceNames.Select(serviceName => new ClusterConfig
|
||||
Timeout = TimeSpan.FromSeconds(5),
|
||||
Path = "/health"
|
||||
},
|
||||
Passive = new()
|
||||
Passive = new PassiveHealthCheckConfig
|
||||
{
|
||||
Enabled = true
|
||||
}
|
||||
@@ -147,7 +153,14 @@ builder.Services
|
||||
.LoadFromMemory(routes, clusters)
|
||||
.AddServiceDiscoveryDestinationResolver();
|
||||
|
||||
builder.Services.AddControllers();
|
||||
builder.Services.AddControllers().AddJsonOptions(options =>
|
||||
{
|
||||
options.JsonSerializerOptions.NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals;
|
||||
options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower;
|
||||
options.JsonSerializerOptions.DictionaryKeyPolicy = JsonNamingPolicy.SnakeCaseLower;
|
||||
|
||||
options.JsonSerializerOptions.ConfigureForNodaTime(DateTimeZoneProviders.Tzdb);
|
||||
});
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
@@ -155,14 +168,16 @@ var forwardedHeadersOptions = new ForwardedHeadersOptions
|
||||
{
|
||||
ForwardedHeaders = ForwardedHeaders.All
|
||||
};
|
||||
forwardedHeadersOptions.KnownNetworks.Clear();
|
||||
forwardedHeadersOptions.KnownIPNetworks.Clear();
|
||||
forwardedHeadersOptions.KnownProxies.Clear();
|
||||
app.UseForwardedHeaders(forwardedHeadersOptions);
|
||||
|
||||
app.UseCors();
|
||||
|
||||
app.UseMiddleware<GatewayReadinessMiddleware>();
|
||||
|
||||
app.MapReverseProxy().RequireRateLimiting("fixed");
|
||||
|
||||
app.MapControllers();
|
||||
|
||||
app.Run();
|
||||
app.Run();
|
||||
@@ -1,13 +1,16 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"Cache": {
|
||||
"Serializer": "MessagePack"
|
||||
},
|
||||
"AllowedHosts": "*",
|
||||
"SiteUrl": "http://localhost:3000",
|
||||
"Client": {
|
||||
"SomeSetting": "SomeValue"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*",
|
||||
"SiteUrl": "http://localhost:3000",
|
||||
"Client": {
|
||||
"SomeSetting": "SomeValue"
|
||||
}
|
||||
}
|
||||
@@ -14,9 +14,7 @@ public static class ServiceCollectionExtensions
|
||||
public static IServiceCollection AddAppServices(this IServiceCollection services)
|
||||
{
|
||||
services.AddDbContext<AppDatabase>();
|
||||
services.AddSingleton<IClock>(SystemClock.Instance);
|
||||
services.AddHttpContextAccessor();
|
||||
services.AddSingleton<ICacheService, CacheServiceRedis>();
|
||||
|
||||
services.AddHttpClient();
|
||||
|
||||
|
||||
@@ -19,6 +19,9 @@
|
||||
"Etcd": {
|
||||
"Insecure": true
|
||||
},
|
||||
"Cache": {
|
||||
"Serializer": "MessagePack"
|
||||
},
|
||||
"Thinking": {
|
||||
"DefaultService": "deepseek-chat",
|
||||
"Services": {
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using DysonNetwork.Pass.Affiliation;
|
||||
using DysonNetwork.Pass.Auth;
|
||||
using DysonNetwork.Pass.Credit;
|
||||
using DysonNetwork.Pass.Permission;
|
||||
using DysonNetwork.Pass.Wallet;
|
||||
using DysonNetwork.Shared.GeoIp;
|
||||
using DysonNetwork.Shared.Auth;
|
||||
using DysonNetwork.Shared.Geometry;
|
||||
using DysonNetwork.Shared.Http;
|
||||
using DysonNetwork.Shared.Models;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
@@ -22,7 +24,8 @@ public class AccountController(
|
||||
SubscriptionService subscriptions,
|
||||
AccountEventService events,
|
||||
SocialCreditService socialCreditService,
|
||||
GeoIpService geo
|
||||
AffiliationSpellService ars,
|
||||
GeoService geo
|
||||
) : ControllerBase
|
||||
{
|
||||
[HttpGet("{name}")]
|
||||
@@ -34,7 +37,7 @@ public class AccountController(
|
||||
.Include(e => e.Badges)
|
||||
.Include(e => e.Profile)
|
||||
.Include(e => e.Contacts.Where(c => c.IsPublic))
|
||||
.Where(a => a.Name == name)
|
||||
.Where(a => EF.Functions.Like(a.Name, name))
|
||||
.FirstOrDefaultAsync();
|
||||
if (account is null) return NotFound(ApiError.NotFound(name, traceId: HttpContext.TraceIdentifier));
|
||||
|
||||
@@ -103,6 +106,52 @@ public class AccountController(
|
||||
[MaxLength(32)] public string Language { get; set; } = "en-us";
|
||||
|
||||
[Required] public string CaptchaToken { get; set; } = string.Empty;
|
||||
|
||||
public string? AffiliationSpell { get; set; }
|
||||
}
|
||||
|
||||
public class AccountCreateValidateRequest
|
||||
{
|
||||
[MinLength(2)]
|
||||
[MaxLength(256)]
|
||||
[RegularExpression(@"^[A-Za-z0-9_-]+$",
|
||||
ErrorMessage = "Name can only contain letters, numbers, underscores, and hyphens.")
|
||||
]
|
||||
public string? Name { get; set; }
|
||||
|
||||
[EmailAddress]
|
||||
[RegularExpression(@"^[^+]+@[^@]+\.[^@]+$", ErrorMessage = "Email address cannot contain '+' symbol.")]
|
||||
[MaxLength(1024)]
|
||||
public string? Email { get; set; }
|
||||
|
||||
public string? AffiliationSpell { get; set; }
|
||||
}
|
||||
|
||||
[HttpPost("validate")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<ActionResult<string>> ValidateCreateAccountRequest(
|
||||
[FromBody] AccountCreateValidateRequest request)
|
||||
{
|
||||
if (request.Name is not null)
|
||||
{
|
||||
if (await accounts.CheckAccountNameHasTaken(request.Name))
|
||||
return BadRequest("Account name has already been taken.");
|
||||
}
|
||||
|
||||
if (request.Email is not null)
|
||||
{
|
||||
if (await accounts.CheckEmailHasBeenUsed(request.Email))
|
||||
return BadRequest("Email has already been used.");
|
||||
}
|
||||
|
||||
if (request.AffiliationSpell is not null)
|
||||
{
|
||||
if (!await ars.CheckAffiliationSpellHasTaken(request.AffiliationSpell))
|
||||
return BadRequest("No affiliation spell has been found.");
|
||||
}
|
||||
|
||||
return Ok("Everything seems good.");
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
@@ -271,10 +320,21 @@ public class AccountController(
|
||||
|
||||
[HttpPost("credits/validate")]
|
||||
[Authorize]
|
||||
[RequiredPermission("maintenance", "credits.validate.perform")]
|
||||
[AskPermission("credits.validate.perform")]
|
||||
public async Task<IActionResult> PerformSocialCreditValidation()
|
||||
{
|
||||
await socialCreditService.ValidateSocialCredits();
|
||||
return Ok();
|
||||
}
|
||||
}
|
||||
|
||||
[HttpDelete("{name}")]
|
||||
[Authorize]
|
||||
[AskPermission("accounts.deletion")]
|
||||
public async Task<IActionResult> AdminDeleteAccount(string name)
|
||||
{
|
||||
var account = await accounts.LookupAccount(name);
|
||||
if (account is null) return NotFound();
|
||||
await accounts.DeleteAccount(account);
|
||||
return Ok();
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using DysonNetwork.Pass.Permission;
|
||||
using DysonNetwork.Pass.Wallet;
|
||||
using DysonNetwork.Shared.Auth;
|
||||
using DysonNetwork.Shared.Http;
|
||||
using DysonNetwork.Shared.Models;
|
||||
using DysonNetwork.Shared.Proto;
|
||||
@@ -82,7 +83,7 @@ public class AccountCurrentController(
|
||||
[MaxLength(4096)] public string? Bio { get; set; }
|
||||
public Shared.Models.UsernameColor? UsernameColor { get; set; }
|
||||
public Instant? Birthday { get; set; }
|
||||
public List<ProfileLink>? Links { get; set; }
|
||||
public List<SnProfileLink>? Links { get; set; }
|
||||
|
||||
[MaxLength(32)] public string? PictureId { get; set; }
|
||||
[MaxLength(32)] public string? BackgroundId { get; set; }
|
||||
@@ -194,7 +195,7 @@ public class AccountCurrentController(
|
||||
}
|
||||
|
||||
[HttpPatch("statuses")]
|
||||
[RequiredPermission("global", "accounts.statuses.update")]
|
||||
[AskPermission("accounts.statuses.update")]
|
||||
public async Task<ActionResult<SnAccountStatus>> UpdateStatus([FromBody] AccountController.StatusRequest request)
|
||||
{
|
||||
if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser) return Unauthorized();
|
||||
@@ -228,7 +229,7 @@ public class AccountCurrentController(
|
||||
}
|
||||
|
||||
[HttpPost("statuses")]
|
||||
[RequiredPermission("global", "accounts.statuses.create")]
|
||||
[AskPermission("accounts.statuses.create")]
|
||||
public async Task<ActionResult<SnAccountStatus>> CreateStatus([FromBody] AccountController.StatusRequest request)
|
||||
{
|
||||
if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser) return Unauthorized();
|
||||
@@ -559,7 +560,7 @@ public class AccountCurrentController(
|
||||
|
||||
[HttpGet("devices")]
|
||||
[Authorize]
|
||||
public async Task<ActionResult<List<SnAuthClientWithChallenge>>> GetDevices()
|
||||
public async Task<ActionResult<List<SnAuthClientWithSessions>>> GetDevices()
|
||||
{
|
||||
if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser ||
|
||||
HttpContext.Items["CurrentSession"] is not SnAuthSession currentSession) return Unauthorized();
|
||||
@@ -570,18 +571,41 @@ public class AccountCurrentController(
|
||||
.Where(device => device.AccountId == currentUser.Id)
|
||||
.ToListAsync();
|
||||
|
||||
var challengeDevices = devices.Select(SnAuthClientWithChallenge.FromClient).ToList();
|
||||
var deviceIds = challengeDevices.Select(x => x.Id).ToList();
|
||||
var sessionDevices = devices.ConvertAll(SnAuthClientWithSessions.FromClient).ToList();
|
||||
var clientIds = sessionDevices.Select(x => x.Id).ToList();
|
||||
|
||||
var authChallenges = await db.AuthChallenges
|
||||
.Where(c => c.ClientId != null && deviceIds.Contains(c.ClientId.Value))
|
||||
.GroupBy(c => c.ClientId)
|
||||
.ToDictionaryAsync(c => c.Key!.Value, c => c.ToList());
|
||||
foreach (var challengeDevice in challengeDevices)
|
||||
if (authChallenges.TryGetValue(challengeDevice.Id, out var challenge))
|
||||
challengeDevice.Challenges = challenge;
|
||||
var authSessions = await db.AuthSessions
|
||||
.Where(c => c.ClientId != null && clientIds.Contains(c.ClientId.Value))
|
||||
.GroupBy(c => c.ClientId!.Value)
|
||||
.ToDictionaryAsync(c => c.Key, c => c.ToList());
|
||||
foreach (var dev in sessionDevices)
|
||||
if (authSessions.TryGetValue(dev.Id, out var challenge))
|
||||
dev.Sessions = challenge;
|
||||
|
||||
return Ok(challengeDevices);
|
||||
return Ok(sessionDevices);
|
||||
}
|
||||
|
||||
[HttpGet("challenges")]
|
||||
[Authorize]
|
||||
public async Task<ActionResult<List<SnAuthChallenge>>> GetChallenges(
|
||||
[FromQuery] int take = 20,
|
||||
[FromQuery] int offset = 0
|
||||
)
|
||||
{
|
||||
if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser) return Unauthorized();
|
||||
|
||||
var query = db.AuthChallenges
|
||||
.Where(challenge => challenge.AccountId == currentUser.Id)
|
||||
.OrderByDescending(c => c.CreatedAt);
|
||||
|
||||
var total = await query.CountAsync();
|
||||
Response.Headers.Append("X-Total", total.ToString());
|
||||
|
||||
var challenges = await query
|
||||
.Skip(offset)
|
||||
.Take(take)
|
||||
.ToListAsync();
|
||||
return Ok(challenges);
|
||||
}
|
||||
|
||||
[HttpGet("sessions")]
|
||||
@@ -595,8 +619,8 @@ public class AccountCurrentController(
|
||||
HttpContext.Items["CurrentSession"] is not SnAuthSession currentSession) return Unauthorized();
|
||||
|
||||
var query = db.AuthSessions
|
||||
.OrderByDescending(x => x.LastGrantedAt)
|
||||
.Include(session => session.Account)
|
||||
.Include(session => session.Challenge)
|
||||
.Where(session => session.Account.Id == currentUser.Id);
|
||||
|
||||
var total = await query.CountAsync();
|
||||
@@ -604,7 +628,6 @@ public class AccountCurrentController(
|
||||
Response.Headers.Append("X-Auth-Session", currentSession.Id.ToString());
|
||||
|
||||
var sessions = await query
|
||||
.OrderByDescending(x => x.LastGrantedAt)
|
||||
.Skip(offset)
|
||||
.Take(take)
|
||||
.ToListAsync();
|
||||
@@ -688,7 +711,7 @@ public class AccountCurrentController(
|
||||
if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser ||
|
||||
HttpContext.Items["CurrentSession"] is not SnAuthSession currentSession) return Unauthorized();
|
||||
|
||||
var device = await db.AuthClients.FirstOrDefaultAsync(d => d.Id == currentSession.Challenge.ClientId);
|
||||
var device = await db.AuthClients.FirstOrDefaultAsync(d => d.Id == currentSession.ClientId);
|
||||
if (device is null) return NotFound();
|
||||
|
||||
try
|
||||
|
||||
@@ -3,7 +3,7 @@ using DysonNetwork.Pass.Wallet;
|
||||
using DysonNetwork.Shared.Cache;
|
||||
using DysonNetwork.Shared.Models;
|
||||
using DysonNetwork.Shared.Proto;
|
||||
using DysonNetwork.Shared.Stream;
|
||||
using DysonNetwork.Shared.Queue;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Localization;
|
||||
using NATS.Client.Core;
|
||||
@@ -313,52 +313,84 @@ public class AccountEventService(
|
||||
CultureInfo.CurrentCulture = cultureInfo;
|
||||
CultureInfo.CurrentUICulture = cultureInfo;
|
||||
|
||||
// Generate 2 positive tips
|
||||
var positiveIndices = Enumerable.Range(1, FortuneTipCount)
|
||||
.OrderBy(_ => Random.Next())
|
||||
.Take(2)
|
||||
.ToList();
|
||||
var tips = positiveIndices.Select(index => new CheckInFortuneTip
|
||||
{
|
||||
IsPositive = true,
|
||||
Title = localizer[$"FortuneTipPositiveTitle_{index}"].Value,
|
||||
Content = localizer[$"FortuneTipPositiveContent_{index}"].Value
|
||||
}).ToList();
|
||||
|
||||
// Generate 2 negative tips
|
||||
var negativeIndices = Enumerable.Range(1, FortuneTipCount)
|
||||
.Except(positiveIndices)
|
||||
.OrderBy(_ => Random.Next())
|
||||
.Take(2)
|
||||
.ToList();
|
||||
tips.AddRange(negativeIndices.Select(index => new CheckInFortuneTip
|
||||
{
|
||||
IsPositive = false,
|
||||
Title = localizer[$"FortuneTipNegativeTitle_{index}"].Value,
|
||||
Content = localizer[$"FortuneTipNegativeContent_{index}"].Value
|
||||
}));
|
||||
|
||||
// The 5 is specialized, keep it alone.
|
||||
// Use weighted random distribution to make all levels reasonably achievable
|
||||
// Weights: Worst: 10%, Worse: 20%, Normal: 40%, Better: 20%, Best: 10%
|
||||
var randomValue = Random.Next(100);
|
||||
var checkInLevel = randomValue switch
|
||||
{
|
||||
< 10 => CheckInResultLevel.Worst, // 0-9: 10% chance
|
||||
< 30 => CheckInResultLevel.Worse, // 10-29: 20% chance
|
||||
< 70 => CheckInResultLevel.Normal, // 30-69: 40% chance
|
||||
< 90 => CheckInResultLevel.Better, // 70-89: 20% chance
|
||||
_ => CheckInResultLevel.Best // 90-99: 10% chance
|
||||
};
|
||||
|
||||
var accountBirthday = await db.AccountProfiles
|
||||
var accountProfile = await db.AccountProfiles
|
||||
.Where(x => x.AccountId == user.Id)
|
||||
.Select(x => x.Birthday)
|
||||
.Select(x => new { x.Birthday, x.TimeZone })
|
||||
.FirstOrDefaultAsync();
|
||||
|
||||
var accountBirthday = accountProfile?.Birthday;
|
||||
|
||||
var now = SystemClock.Instance.GetCurrentInstant().InUtc().Date;
|
||||
if (accountBirthday.HasValue && accountBirthday.Value.InUtc().Date == now)
|
||||
var now = SystemClock.Instance.GetCurrentInstant();
|
||||
|
||||
var userTimeZone = DateTimeZone.Utc;
|
||||
if (!string.IsNullOrEmpty(accountProfile?.TimeZone))
|
||||
{
|
||||
userTimeZone = DateTimeZoneProviders.Tzdb.GetZoneOrNull(accountProfile.TimeZone) ?? DateTimeZone.Utc;
|
||||
}
|
||||
|
||||
var todayInUserTz = now.InZone(userTimeZone).Date;
|
||||
var birthdayDate = accountBirthday?.InZone(userTimeZone).Date;
|
||||
|
||||
var isBirthday = birthdayDate.HasValue &&
|
||||
birthdayDate.Value.Month == todayInUserTz.Month &&
|
||||
birthdayDate.Value.Day == todayInUserTz.Day;
|
||||
|
||||
List<CheckInFortuneTip> tips;
|
||||
CheckInResultLevel checkInLevel;
|
||||
|
||||
if (isBirthday)
|
||||
{
|
||||
// Skip random logic and tips generation for birthday
|
||||
checkInLevel = CheckInResultLevel.Special;
|
||||
tips = [
|
||||
new CheckInFortuneTip()
|
||||
{
|
||||
IsPositive = true,
|
||||
Title = localizer["FortuneTipSpecialTitle_Birthday"].Value,
|
||||
Content = localizer["FortuneTipSpecialContent_Birthday", user.Nick].Value,
|
||||
}
|
||||
];
|
||||
}
|
||||
else
|
||||
{
|
||||
// Generate 2 positive tips
|
||||
var positiveIndices = Enumerable.Range(1, FortuneTipCount)
|
||||
.OrderBy(_ => Random.Next())
|
||||
.Take(2)
|
||||
.ToList();
|
||||
tips = positiveIndices.Select(index => new CheckInFortuneTip
|
||||
{
|
||||
IsPositive = true,
|
||||
Title = localizer[$"FortuneTipPositiveTitle_{index}"].Value,
|
||||
Content = localizer[$"FortuneTipPositiveContent_{index}"].Value
|
||||
}).ToList();
|
||||
|
||||
// Generate 2 negative tips
|
||||
var negativeIndices = Enumerable.Range(1, FortuneTipCount)
|
||||
.Except(positiveIndices)
|
||||
.OrderBy(_ => Random.Next())
|
||||
.Take(2)
|
||||
.ToList();
|
||||
tips.AddRange(negativeIndices.Select(index => new CheckInFortuneTip
|
||||
{
|
||||
IsPositive = false,
|
||||
Title = localizer[$"FortuneTipNegativeTitle_{index}"].Value,
|
||||
Content = localizer[$"FortuneTipNegativeContent_{index}"].Value
|
||||
}));
|
||||
|
||||
// The 5 is specialized, keep it alone.
|
||||
// Use weighted random distribution to make all levels reasonably achievable
|
||||
// Weights: Worst: 10%, Worse: 20%, Normal: 40%, Better: 20%, Best: 10%
|
||||
var randomValue = Random.Next(100);
|
||||
checkInLevel = randomValue switch
|
||||
{
|
||||
< 10 => CheckInResultLevel.Worst, // 0-9: 10% chance
|
||||
< 30 => CheckInResultLevel.Worse, // 10-29: 20% chance
|
||||
< 70 => CheckInResultLevel.Normal, // 30-69: 40% chance
|
||||
< 90 => CheckInResultLevel.Better, // 70-89: 20% chance
|
||||
_ => CheckInResultLevel.Best // 90-99: 10% chance
|
||||
};
|
||||
}
|
||||
|
||||
var result = new SnCheckInResult
|
||||
{
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Globalization;
|
||||
using DysonNetwork.Pass.Affiliation;
|
||||
using DysonNetwork.Pass.Auth.OpenId;
|
||||
using DysonNetwork.Pass.Localization;
|
||||
using DysonNetwork.Pass.Mailer;
|
||||
@@ -7,7 +8,7 @@ using DysonNetwork.Shared.Cache;
|
||||
using DysonNetwork.Shared.Data;
|
||||
using DysonNetwork.Shared.Models;
|
||||
using DysonNetwork.Shared.Proto;
|
||||
using DysonNetwork.Shared.Stream;
|
||||
using DysonNetwork.Shared.Queue;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Localization;
|
||||
using NATS.Client.Core;
|
||||
@@ -24,6 +25,7 @@ public class AccountService(
|
||||
FileService.FileServiceClient files,
|
||||
FileReferenceService.FileReferenceServiceClient fileRefs,
|
||||
AccountUsernameService uname,
|
||||
AffiliationSpellService ars,
|
||||
EmailService mailer,
|
||||
RingService.RingServiceClient pusher,
|
||||
IStringLocalizer<NotificationResource> localizer,
|
||||
@@ -54,11 +56,13 @@ public class AccountService(
|
||||
|
||||
public async Task<SnAccount?> LookupAccount(string probe)
|
||||
{
|
||||
var account = await db.Accounts.Where(a => a.Name == probe).FirstOrDefaultAsync();
|
||||
var account = await db.Accounts.Where(a => EF.Functions.ILike(a.Name, probe)).FirstOrDefaultAsync();
|
||||
if (account is not null) return account;
|
||||
|
||||
var contact = await db.AccountContacts
|
||||
.Where(c => c.Content == probe)
|
||||
.Where(c => c.Type == Shared.Models.AccountContactType.Email ||
|
||||
c.Type == Shared.Models.AccountContactType.PhoneNumber)
|
||||
.Where(c => EF.Functions.ILike(c.Content, probe))
|
||||
.Include(c => c.Account)
|
||||
.FirstOrDefaultAsync();
|
||||
return contact?.Account;
|
||||
@@ -81,6 +85,17 @@ public class AccountService(
|
||||
return profile?.Level;
|
||||
}
|
||||
|
||||
public async Task<bool> CheckAccountNameHasTaken(string name)
|
||||
{
|
||||
return await db.Accounts.AnyAsync(a => EF.Functions.ILike(a.Name, name));
|
||||
}
|
||||
|
||||
public async Task<bool> CheckEmailHasBeenUsed(string email)
|
||||
{
|
||||
return await db.AccountContacts.AnyAsync(c =>
|
||||
c.Type == Shared.Models.AccountContactType.Email && EF.Functions.ILike(c.Content, email));
|
||||
}
|
||||
|
||||
public async Task<SnAccount> CreateAccount(
|
||||
string name,
|
||||
string nick,
|
||||
@@ -88,12 +103,12 @@ public class AccountService(
|
||||
string? password,
|
||||
string language = "en-US",
|
||||
string region = "en",
|
||||
string? affiliationSpell = null,
|
||||
bool isEmailVerified = false,
|
||||
bool isActivated = false
|
||||
)
|
||||
{
|
||||
var dupeNameCount = await db.Accounts.Where(a => a.Name == name).CountAsync();
|
||||
if (dupeNameCount > 0)
|
||||
if (await CheckAccountNameHasTaken(name))
|
||||
throw new InvalidOperationException("Account name has already been taken.");
|
||||
|
||||
var dupeEmailCount = await db.AccountContacts
|
||||
@@ -101,7 +116,7 @@ public class AccountService(
|
||||
).CountAsync();
|
||||
if (dupeEmailCount > 0)
|
||||
throw new InvalidOperationException("Account email has already been used.");
|
||||
|
||||
|
||||
var account = new SnAccount
|
||||
{
|
||||
Name = name,
|
||||
@@ -110,7 +125,7 @@ public class AccountService(
|
||||
Region = region,
|
||||
Contacts =
|
||||
[
|
||||
new()
|
||||
new SnAccountContact
|
||||
{
|
||||
Type = Shared.Models.AccountContactType.Email,
|
||||
Content = email,
|
||||
@@ -132,6 +147,9 @@ public class AccountService(
|
||||
Profile = new SnAccountProfile()
|
||||
};
|
||||
|
||||
if (affiliationSpell is not null)
|
||||
await ars.CreateAffiliationResult(affiliationSpell, $"account:{account.Id}");
|
||||
|
||||
if (isActivated)
|
||||
{
|
||||
account.ActivatedAt = SystemClock.Instance.GetCurrentInstant();
|
||||
@@ -140,7 +158,7 @@ public class AccountService(
|
||||
{
|
||||
db.PermissionGroupMembers.Add(new SnPermissionGroupMember
|
||||
{
|
||||
Actor = $"user:{account.Id}",
|
||||
Actor = account.Id.ToString(),
|
||||
Group = defaultGroup
|
||||
});
|
||||
}
|
||||
@@ -181,10 +199,7 @@ public class AccountService(
|
||||
displayName,
|
||||
userInfo.Email,
|
||||
null,
|
||||
"en-US",
|
||||
"en",
|
||||
userInfo.EmailVerified,
|
||||
userInfo.EmailVerified
|
||||
isEmailVerified: userInfo.EmailVerified
|
||||
);
|
||||
}
|
||||
|
||||
@@ -274,7 +289,8 @@ public class AccountService(
|
||||
return isExists;
|
||||
}
|
||||
|
||||
public async Task<SnAccountAuthFactor?> CreateAuthFactor(SnAccount account, Shared.Models.AccountAuthFactorType type, string? secret)
|
||||
public async Task<SnAccountAuthFactor?> CreateAuthFactor(SnAccount account,
|
||||
Shared.Models.AccountAuthFactorType type, string? secret)
|
||||
{
|
||||
SnAccountAuthFactor? factor = null;
|
||||
switch (type)
|
||||
@@ -352,7 +368,8 @@ public class AccountService(
|
||||
public async Task<SnAccountAuthFactor> EnableAuthFactor(SnAccountAuthFactor factor, string? code)
|
||||
{
|
||||
if (factor.EnabledAt is not null) throw new ArgumentException("The factor has been enabled.");
|
||||
if (factor.Type is Shared.Models.AccountAuthFactorType.Password or Shared.Models.AccountAuthFactorType.TimedCode)
|
||||
if (factor.Type is Shared.Models.AccountAuthFactorType.Password
|
||||
or Shared.Models.AccountAuthFactorType.TimedCode)
|
||||
{
|
||||
if (code is null || !factor.VerifyPassword(code))
|
||||
throw new InvalidOperationException(
|
||||
@@ -508,9 +525,7 @@ public class AccountService(
|
||||
|
||||
private async Task<bool> IsDeviceActive(Guid id)
|
||||
{
|
||||
return await db.AuthSessions
|
||||
.Include(s => s.Challenge)
|
||||
.AnyAsync(s => s.Challenge.ClientId == id);
|
||||
return await db.AuthSessions.AnyAsync(s => s.ClientId == id);
|
||||
}
|
||||
|
||||
public async Task<SnAuthClient> UpdateDeviceName(SnAccount account, string deviceId, string label)
|
||||
@@ -529,8 +544,7 @@ public class AccountService(
|
||||
public async Task DeleteSession(SnAccount account, Guid sessionId)
|
||||
{
|
||||
var session = await db.AuthSessions
|
||||
.Include(s => s.Challenge)
|
||||
.ThenInclude(s => s.Client)
|
||||
.Include(s => s.Client)
|
||||
.Where(s => s.Id == sessionId && s.AccountId == account.Id)
|
||||
.FirstOrDefaultAsync();
|
||||
if (session is null) throw new InvalidOperationException("Session was not found.");
|
||||
@@ -539,11 +553,11 @@ public class AccountService(
|
||||
db.AuthSessions.Remove(session);
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
if (session.Challenge.ClientId.HasValue)
|
||||
if (session.ClientId.HasValue)
|
||||
{
|
||||
if (!await IsDeviceActive(session.Challenge.ClientId.Value))
|
||||
if (!await IsDeviceActive(session.ClientId.Value))
|
||||
await pusher.UnsubscribePushNotificationsAsync(new UnsubscribePushNotificationsRequest()
|
||||
{ DeviceId = session.Challenge.Client!.DeviceId }
|
||||
{ DeviceId = session.Client!.DeviceId }
|
||||
);
|
||||
}
|
||||
|
||||
@@ -564,15 +578,13 @@ public class AccountService(
|
||||
);
|
||||
|
||||
var sessions = await db.AuthSessions
|
||||
.Include(s => s.Challenge)
|
||||
.Where(s => s.Challenge.ClientId == device.Id && s.AccountId == account.Id)
|
||||
.Where(s => s.ClientId == device.Id && s.AccountId == account.Id)
|
||||
.ToListAsync();
|
||||
|
||||
// The current session should be included in the sessions' list
|
||||
var now = SystemClock.Instance.GetCurrentInstant();
|
||||
await db.AuthSessions
|
||||
.Include(s => s.Challenge)
|
||||
.Where(s => s.Challenge.ClientId == device.Id)
|
||||
.Where(s => s.ClientId == device.Id)
|
||||
.ExecuteUpdateAsync(p => p.SetProperty(s => s.DeletedAt, s => now));
|
||||
|
||||
db.AuthClients.Remove(device);
|
||||
@@ -582,7 +594,8 @@ public class AccountService(
|
||||
await cache.RemoveAsync($"{AuthService.AuthCachePrefix}{item.Id}");
|
||||
}
|
||||
|
||||
public async Task<SnAccountContact> CreateContactMethod(SnAccount account, Shared.Models.AccountContactType type, string content)
|
||||
public async Task<SnAccountContact> CreateContactMethod(SnAccount account, Shared.Models.AccountContactType type,
|
||||
string content)
|
||||
{
|
||||
var isExists = await db.AccountContacts
|
||||
.Where(x => x.AccountId == account.Id && x.Type == type && x.Content == content)
|
||||
@@ -644,7 +657,8 @@ public class AccountService(
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<SnAccountContact> SetContactMethodPublic(SnAccount account, SnAccountContact contact, bool isPublic)
|
||||
public async Task<SnAccountContact> SetContactMethodPublic(SnAccount account, SnAccountContact contact,
|
||||
bool isPublic)
|
||||
{
|
||||
contact.IsPublic = isPublic;
|
||||
db.AccountContacts.Update(contact);
|
||||
|
||||
@@ -24,15 +24,16 @@ public class AccountServiceGrpc(
|
||||
public override async Task<Shared.Proto.Account> GetAccount(GetAccountRequest request, ServerCallContext context)
|
||||
{
|
||||
if (!Guid.TryParse(request.Id, out var accountId))
|
||||
throw new RpcException(new Grpc.Core.Status(StatusCode.InvalidArgument, "Invalid account ID format"));
|
||||
throw new RpcException(new Status(StatusCode.InvalidArgument, "Invalid account ID format"));
|
||||
|
||||
var account = await _db.Accounts
|
||||
.AsNoTracking()
|
||||
.Include(a => a.Profile)
|
||||
.Include(a => a.Contacts.Where(c => c.IsPublic))
|
||||
.FirstOrDefaultAsync(a => a.Id == accountId);
|
||||
|
||||
if (account == null)
|
||||
throw new RpcException(new Grpc.Core.Status(StatusCode.NotFound, $"Account {request.Id} not found"));
|
||||
throw new RpcException(new Status(StatusCode.NotFound, $"Account {request.Id} not found"));
|
||||
|
||||
var perk = await subscriptions.GetPerkSubscriptionAsync(account.Id);
|
||||
account.PerkSubscription = perk?.ToReference();
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
using DysonNetwork.Shared.Cache;
|
||||
using DysonNetwork.Shared.GeoIp;
|
||||
using DysonNetwork.Shared.Geometry;
|
||||
using DysonNetwork.Shared.Models;
|
||||
|
||||
namespace DysonNetwork.Pass.Account;
|
||||
|
||||
public class ActionLogService(GeoIpService geo, FlushBufferService fbs)
|
||||
public class ActionLogService(GeoService geo, FlushBufferService fbs)
|
||||
{
|
||||
public void CreateActionLog(Guid accountId, string action, Dictionary<string, object> meta)
|
||||
{
|
||||
|
||||
141
DysonNetwork.Pass/Account/FortuneSayingController.cs
Normal file
141
DysonNetwork.Pass/Account/FortuneSayingController.cs
Normal file
@@ -0,0 +1,141 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace DysonNetwork.Pass.Account;
|
||||
|
||||
public record FortuneSaying(
|
||||
string Content,
|
||||
string Source,
|
||||
string Language
|
||||
);
|
||||
|
||||
[ApiController]
|
||||
[Route("/api/fortune")]
|
||||
public class FortuneSayingController : ControllerBase
|
||||
{
|
||||
private static readonly FortuneSaying[] Sayings =
|
||||
[
|
||||
// Chinese sayings
|
||||
new("天行健,君子以自强不息。", "周易", "zh"),
|
||||
new("地势坤,君子以厚德载物。", "周易", "zh"),
|
||||
new("天道酬勤。", "谚语", "zh"),
|
||||
new("天时不如地利,地利不如人和。", "孟子", "zh"),
|
||||
new("君子有三畏:畏天命,畏大人,畏圣人之言。", "论语", "zh"),
|
||||
new("己所不欲,勿施于人。", "孔子", "zh"),
|
||||
new("学而时习之,不亦说乎?", "论语", "zh"),
|
||||
new("有朋自远方来,不亦乐乎?", "论语", "zh"),
|
||||
new("人不知而不愠,不亦君子乎?", "论语", "zh"),
|
||||
new("不愤不启,不悱不发。", "论语", "zh"),
|
||||
new("温故而知新,可以为师矣。", "论语", "zh"),
|
||||
new("见贤思齐焉,见不贤而内自省也。", "论语", "zh"),
|
||||
new("君子坦荡荡,小人长戚戚。", "论语", "zh"),
|
||||
new("道不远人。人之为道而远人,不可以为道。", "论语", "zh"),
|
||||
new("巧言令色,鲜矣仁。", "论语", "zh"),
|
||||
new("岁寒,然后知松柏之后凋也。", "孔子", "zh"),
|
||||
new("志于道,据于德,依于仁,游于艺。", "论语", "zh"),
|
||||
new("博学而笃志,切问而近思。", "论语", "zh"),
|
||||
new("知之者不如好之者,好之者不如乐之者。", "论语", "zh"),
|
||||
new("君子欲讷于言而敏于行。", "论语", "zh"),
|
||||
new("君子周而不比,小人比而不周。", "论语", "zh"),
|
||||
new("君子喻于义,小人喻于利。", "论语", "zh"),
|
||||
new("君子怀德,小人怀土。", "论语", "zh"),
|
||||
new("君子矜而不争,群而不党。", "论语", "zh"),
|
||||
new("君子和而不同,小人同而不和。", "论语", "zh"),
|
||||
new("君子泰而不骄,小人骄而不泰。", "论语", "zh"),
|
||||
new("君子谋道不谋食。", "论语", "zh"),
|
||||
new("君子食无求饱,居无求安。", "论语", "zh"),
|
||||
new("君子学以致其道。", "论语", "zh"),
|
||||
new("君子耻其言而过其行。", "论语", "zh"),
|
||||
new("君子敬而无失,与人恭而有礼。", "论语", "zh"),
|
||||
new("君子求诸己,小人求诸人。", "论语", "zh"),
|
||||
new("君子慎独。", "论语", "zh"),
|
||||
new("君子不以言举人,不以人废言。", "论语", "zh"),
|
||||
new("君子不器。", "论语", "zh"),
|
||||
new("君子有终身之忧,无一朝之患。", "论语", "zh"),
|
||||
new("君子固穷,小人穷斯滥矣。", "论语", "zh"),
|
||||
new("君子疾没世而名不称焉。", "论语", "zh"),
|
||||
new("君子而不仁者有矣夫,未有小人而仁者也。", "论语", "zh"),
|
||||
new("君子义以为质,礼以行之,逊以出之,信以成之。", "论语", "zh"),
|
||||
new("君子之德风,小人之德草。", "论语", "zh"),
|
||||
new("君子之过也,如日月之食焉。", "论语", "zh"),
|
||||
new("君子之言,寡而实;小人之言,多而虚。", "论语", "zh"),
|
||||
new("君子之行,静以修身;小人之行,躁以求名。", "论语", "zh"),
|
||||
new("君子之交淡若水,小人之交甘若醴。", "论语", "zh"),
|
||||
new("君子之泽,五世而斩;小人之泽,亦五世而斩。", "孟子", "zh"),
|
||||
new("君子有三乐,而王天下不与存焉。", "孟子", "zh"),
|
||||
new("君子有三戒:少之时,血气未定,戒之在色;及其壮也,血气方刚,戒之在斗;及其老也,血气既衰,戒之在得。", "论语", "zh"),
|
||||
new("君子莫大乎与人为善。", "孟子", "zh"),
|
||||
new("君子远庖厨。", "孟子", "zh"),
|
||||
// English sayings
|
||||
new("The only way to do great work is to love what you do.", "Steve Jobs", "en"),
|
||||
new("Believe you can and you're halfway there.", "Theodore Roosevelt", "en"),
|
||||
new("The future belongs to those who believe in the beauty of their dreams.", "Eleanor Roosevelt", "en"),
|
||||
new("You miss 100% of the shots you don't take.", "Wayne Gretzky", "en"),
|
||||
new("The best way to predict the future is to create it.", "Peter Drucker", "en"),
|
||||
new("Fortune favors the bold.", "Virgil", "en"),
|
||||
new("Luck is what happens when preparation meets opportunity.", "Seneca", "en"),
|
||||
new("The harder you work, the luckier you get.", "Gary Player", "en"),
|
||||
new("Success is not final, failure is not fatal: It is the courage to continue that counts.", "Winston Churchill", "en"),
|
||||
new("The pessimist complains about the wind; the optimist expects it to change; the realist adjusts the sails.", "William Arthur Ward", "en"),
|
||||
new("The road to success is dotted with many tempting parking spaces.", "Will Rogers", "en"),
|
||||
new("Don't watch the clock; do what it does. Keep going.", "Sam Levenson", "en"),
|
||||
new("The only limit to our realization of tomorrow will be our doubts of today.", "Franklin D. Roosevelt", "en"),
|
||||
new("Your time is limited, so don't waste it living someone else's life.", "Steve Jobs", "en"),
|
||||
new("The way to get started is to quit talking and begin doing.", "Walt Disney", "en"),
|
||||
new("If you look at what you have in life, you'll always have more.", "Oprah Winfrey", "en"),
|
||||
new("The best revenge is massive success.", "Frank Sinatra", "en"),
|
||||
new("You must do the things you think you cannot do.", "Eleanor Roosevelt", "en"),
|
||||
new("Keep your face always toward the sunshine—and shadows will fall behind you.", "Walt Whitman", "en"),
|
||||
new("The greatest glory in living lies not in never falling, but in rising every time we fall.", "Nelson Mandela", "en"),
|
||||
new("Life is what happens to you while you're busy making other plans.", "John Lennon", "en"),
|
||||
new("The secret of getting ahead is getting started.", "Mark Twain", "en"),
|
||||
new("Believe in yourself and all that you are.", "Christian D. Larson", "en"),
|
||||
new("The only person you are destined to become is the person you decide to be.", "Ralph Waldo Emerson", "en"),
|
||||
new("Dream big and dare to fail.", "Norman Vaughan", "en"),
|
||||
new("What lies behind us and what lies before us are tiny matters compared to what lies within us.", "Ralph Waldo Emerson", "en"),
|
||||
new("You can't use up creativity. The more you use, the more you have.", "Maya Angelou", "en"),
|
||||
new("The mind is everything. What you think you become.", "Buddha", "en"),
|
||||
new("The best time to plant a tree was 20 years ago. The second best time is now.", "Chinese Proverb", "en"),
|
||||
new("Fall seven times, stand up eight.", "Japanese Proverb", "en"),
|
||||
new("The journey of a thousand miles begins with a single step.", "Lao Tzu", "en"),
|
||||
new("Be not afraid of growing slowly, be afraid only of standing still.", "Chinese Proverb", "en"),
|
||||
new("A bird does not sing because it has an answer. It sings because it has a song.", "Chinese Proverb", "en"),
|
||||
new("Do not dwell in the past, do not dream of the future, concentrate the mind on the present moment.", "Buddha", "en"),
|
||||
new("The best and most beautiful things in the world cannot be seen or even touched - they must be felt with the heart.", "Helen Keller", "en"),
|
||||
new("Keep your eyes on the stars, and your feet on the ground.", "Theodore Roosevelt", "en"),
|
||||
new("The only true wisdom is in knowing you know nothing.", "Socrates", "en"),
|
||||
new("In the middle of every difficulty lies opportunity.", "Albert Einstein", "en"),
|
||||
new("What you get by achieving your goals is not as important as what you become by achieving your goals.", "Zig Ziglar", "en"),
|
||||
new("The purpose of life is a life of purpose.", "Robert Byrne", "en"),
|
||||
new("You become what you believe.", "Oprah Winfrey", "en"),
|
||||
new("The difference between a successful person and others is not a lack of strength, not a lack of knowledge, but rather a lack in will.", "Vince Lombardi", "en"),
|
||||
new("The only way to make sense out of change is to plunge into it, move with it, and join the dance.", "Alan Watts", "en"),
|
||||
new("Your work is going to fill a large part of your life, and the only way to be truly satisfied is to do what you believe is great work.", "Steve Jobs", "en"),
|
||||
new("The man who has confidence in himself gains the confidence of others.", "Hasidic Proverb", "en"),
|
||||
new("Courage is not the absence of fear, but rather the assessment that something else is more important than fear.", "Franklin D. Roosevelt", "en"),
|
||||
new("The best preparation for tomorrow is doing your best today.", "H. Jackson Brown Jr.", "en"),
|
||||
new("Believe in the power of your own voice. The more you use it, the stronger it becomes.", "Unknown", "en"),
|
||||
new("Opportunities don't happen, you create them.", "Chris Grosser", "en")
|
||||
];
|
||||
|
||||
[HttpGet]
|
||||
public ActionResult<List<FortuneSaying>> ListFortunes()
|
||||
{
|
||||
return Ok(Sayings);
|
||||
}
|
||||
|
||||
[HttpGet("random")]
|
||||
public ActionResult<List<FortuneSaying>> GetRandomFortunes([FromQuery] string? language)
|
||||
{
|
||||
var filteredSayings = string.IsNullOrEmpty(language)
|
||||
? Sayings
|
||||
: Sayings.Where(s => s.Language.Equals(language, StringComparison.OrdinalIgnoreCase)).ToArray();
|
||||
|
||||
if (filteredSayings.Length == 0)
|
||||
return NotFound("No fortunes found for the specified language.");
|
||||
|
||||
var random = new Random();
|
||||
var randomSaying = filteredSayings[random.Next(filteredSayings.Length)];
|
||||
|
||||
return Ok(new List<FortuneSaying> { randomSaying });
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
using DysonNetwork.Shared.Models;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
@@ -7,17 +9,31 @@ namespace DysonNetwork.Pass.Account;
|
||||
[Route("/api/spells")]
|
||||
public class MagicSpellController(AppDatabase db, MagicSpellService sp) : ControllerBase
|
||||
{
|
||||
[HttpPost("activation/resend")]
|
||||
[Authorize]
|
||||
public async Task<ActionResult> ResendActivationMagicSpell()
|
||||
{
|
||||
if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser) return Unauthorized();
|
||||
|
||||
var spell = await db.MagicSpells.FirstOrDefaultAsync(s =>
|
||||
s.Type == MagicSpellType.AccountActivation && s.AccountId == currentUser.Id);
|
||||
if (spell is null) return BadRequest("Unable to find activation magic spell.");
|
||||
|
||||
await sp.NotifyMagicSpell(spell, true);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[HttpPost("{spellId:guid}/resend")]
|
||||
public async Task<ActionResult> ResendMagicSpell(Guid spellId)
|
||||
{
|
||||
var spell = db.MagicSpells.FirstOrDefault(x => x.Id == spellId);
|
||||
if (spell == null)
|
||||
return NotFound();
|
||||
|
||||
|
||||
await sp.NotifyMagicSpell(spell, true);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
|
||||
[HttpGet("{spellWord}")]
|
||||
public async Task<ActionResult> GetMagicSpell(string spellWord)
|
||||
{
|
||||
@@ -38,7 +54,8 @@ public class MagicSpellController(AppDatabase db, MagicSpellService sp) : Contro
|
||||
}
|
||||
|
||||
[HttpPost("{spellWord}/apply")]
|
||||
public async Task<ActionResult> ApplyMagicSpell([FromRoute] string spellWord, [FromBody] MagicSpellApplyRequest? request)
|
||||
public async Task<ActionResult> ApplyMagicSpell([FromRoute] string spellWord,
|
||||
[FromBody] MagicSpellApplyRequest? request)
|
||||
{
|
||||
var word = Uri.UnescapeDataString(spellWord);
|
||||
var spell = await db.MagicSpells
|
||||
@@ -59,6 +76,7 @@ public class MagicSpellController(AppDatabase db, MagicSpellService sp) : Contro
|
||||
{
|
||||
return BadRequest(ex.Message);
|
||||
}
|
||||
|
||||
return Ok();
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,7 @@ public class MagicSpellService(
|
||||
Dictionary<string, object> meta,
|
||||
Instant? expiredAt = null,
|
||||
Instant? affectedAt = null,
|
||||
string? code = null,
|
||||
bool preventRepeat = false
|
||||
)
|
||||
{
|
||||
@@ -41,7 +42,7 @@ public class MagicSpellService(
|
||||
return existingSpell;
|
||||
}
|
||||
|
||||
var spellWord = _GenerateRandomString(128);
|
||||
var spellWord = code ?? _GenerateRandomString(128);
|
||||
var spell = new SnMagicSpell
|
||||
{
|
||||
Spell = spellWord,
|
||||
@@ -193,7 +194,7 @@ public class MagicSpellService(
|
||||
{
|
||||
db.PermissionGroupMembers.Add(new SnPermissionGroupMember
|
||||
{
|
||||
Actor = $"user:{account.Id}",
|
||||
Actor = account.Id.ToString(),
|
||||
Group = defaultGroup
|
||||
});
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ public class NotableDay
|
||||
public Instant Date { get; set; }
|
||||
public string? LocalName { get; set; }
|
||||
public string? GlobalName { get; set; }
|
||||
public string? LocalizableKey { get; set; }
|
||||
public string? CountryCode { get; set; }
|
||||
public NotableHolidayType[] Holidays { get; set; } = [];
|
||||
|
||||
|
||||
@@ -77,4 +77,52 @@ public class NotableDaysController(NotableDaysService days) : ControllerBase
|
||||
}
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
[HttpGet("{regionCode}/current")]
|
||||
public async Task<ActionResult<NotableDay?>> GetCurrentHoliday(string regionCode)
|
||||
{
|
||||
var result = await days.GetCurrentHoliday(regionCode);
|
||||
if (result == null)
|
||||
{
|
||||
return NotFound("No holiday today");
|
||||
}
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
[HttpGet("me/current")]
|
||||
[Authorize]
|
||||
public async Task<ActionResult<NotableDay?>> GetAccountCurrentHoliday()
|
||||
{
|
||||
if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser) return Unauthorized();
|
||||
|
||||
var region = currentUser.Region;
|
||||
if (string.IsNullOrWhiteSpace(region)) region = "us";
|
||||
|
||||
var result = await days.GetCurrentHoliday(region);
|
||||
if (result == null)
|
||||
{
|
||||
return NotFound("No holiday today");
|
||||
}
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
[HttpGet("{regionCode}/recent")]
|
||||
public async Task<ActionResult<List<NotableDay>>> GetRecentNotableDay(string regionCode)
|
||||
{
|
||||
var result = await days.GetCurrentAndNextHoliday(regionCode);
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
[HttpGet("me/recent")]
|
||||
[Authorize]
|
||||
public async Task<ActionResult<List<NotableDay>>> GetAccountRecentNotableDay()
|
||||
{
|
||||
if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser) return Unauthorized();
|
||||
|
||||
var region = currentUser.Region;
|
||||
if (string.IsNullOrWhiteSpace(region)) region = "us";
|
||||
|
||||
var result = await days.GetCurrentAndNextHoliday(region);
|
||||
return Ok(result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,12 +27,123 @@ public class NotableDaysService(ICacheService cache)
|
||||
var holidays = await holidayClient.GetHolidaysAsync(year.Value, regionCode);
|
||||
var days = holidays?.Select(NotableDay.FromNagerHoliday).ToList() ?? [];
|
||||
|
||||
// Add global holidays that are available for all regions
|
||||
var globalDays = GetGlobalHolidays(year.Value);
|
||||
foreach (var globalDay in globalDays.Where(globalDay =>
|
||||
!days.Any(d => d.Date.Equals(globalDay.Date) && d.GlobalName == globalDay.GlobalName)))
|
||||
{
|
||||
days.Add(globalDay);
|
||||
}
|
||||
|
||||
// Cache the result for 1 day (holiday data doesn't change frequently)
|
||||
await cache.SetAsync(cacheKey, days, TimeSpan.FromDays(1));
|
||||
|
||||
return days;
|
||||
}
|
||||
|
||||
private static List<NotableDay> GetGlobalHolidays(int year)
|
||||
{
|
||||
var globalDays = new List<NotableDay>();
|
||||
|
||||
// Christmas Day - December 25
|
||||
var christmas = new NotableDay
|
||||
{
|
||||
Date = Instant.FromDateTimeUtc(new DateTime(year, 12, 25, 0, 0, 0, DateTimeKind.Utc)),
|
||||
LocalName = "Christmas",
|
||||
GlobalName = "Christmas",
|
||||
LocalizableKey = "Christmas",
|
||||
CountryCode = null,
|
||||
Holidays = [NotableHolidayType.Public]
|
||||
};
|
||||
globalDays.Add(christmas);
|
||||
|
||||
// New Year's Day - January 1
|
||||
var newYear = new NotableDay
|
||||
{
|
||||
Date = Instant.FromDateTimeUtc(new DateTime(year, 1, 1, 0, 0, 0, DateTimeKind.Utc)),
|
||||
LocalName = "New Year's Day",
|
||||
GlobalName = "New Year's Day",
|
||||
LocalizableKey = "NewYear",
|
||||
CountryCode = null,
|
||||
Holidays = [NotableHolidayType.Public]
|
||||
};
|
||||
globalDays.Add(newYear);
|
||||
|
||||
// Valentine's Day - February 14
|
||||
var valentine = new NotableDay
|
||||
{
|
||||
Date = Instant.FromDateTimeUtc(new DateTime(year, 2, 14, 0, 0, 0, DateTimeKind.Utc)),
|
||||
LocalName = "Valentine's Day",
|
||||
GlobalName = "Valentine's Day",
|
||||
LocalizableKey = "ValentineDay",
|
||||
CountryCode = null,
|
||||
Holidays = [NotableHolidayType.Observance]
|
||||
};
|
||||
globalDays.Add(valentine);
|
||||
|
||||
// April Fools' Day - April 1
|
||||
var aprilFools = new NotableDay
|
||||
{
|
||||
Date = Instant.FromDateTimeUtc(new DateTime(year, 4, 1, 0, 0, 0, DateTimeKind.Utc)),
|
||||
LocalName = "April Fools' Day",
|
||||
GlobalName = "April Fools' Day",
|
||||
LocalizableKey = "AprilFoolsDay",
|
||||
CountryCode = null,
|
||||
Holidays = [NotableHolidayType.Observance]
|
||||
};
|
||||
globalDays.Add(aprilFools);
|
||||
|
||||
// International Workers' Day - May 1
|
||||
var workersDay = new NotableDay
|
||||
{
|
||||
Date = Instant.FromDateTimeUtc(new DateTime(year, 5, 1, 0, 0, 0, DateTimeKind.Utc)),
|
||||
LocalName = "International Workers' Day",
|
||||
GlobalName = "International Workers' Day",
|
||||
LocalizableKey = "WorkersDay",
|
||||
CountryCode = null,
|
||||
Holidays = [NotableHolidayType.Public]
|
||||
};
|
||||
globalDays.Add(workersDay);
|
||||
|
||||
// Children's Day - June 1
|
||||
var childrenDay = new NotableDay
|
||||
{
|
||||
Date = Instant.FromDateTimeUtc(new DateTime(year, 6, 1, 0, 0, 0, DateTimeKind.Utc)),
|
||||
LocalName = "Children's Day",
|
||||
GlobalName = "Children's Day",
|
||||
LocalizableKey = "ChildrenDay",
|
||||
CountryCode = null,
|
||||
Holidays = [NotableHolidayType.Public]
|
||||
};
|
||||
globalDays.Add(childrenDay);
|
||||
|
||||
// World Environment Day - June 5
|
||||
var environmentDay = new NotableDay
|
||||
{
|
||||
Date = Instant.FromDateTimeUtc(new DateTime(year, 6, 5, 0, 0, 0, DateTimeKind.Utc)),
|
||||
LocalName = "World Environment Day",
|
||||
GlobalName = "World Environment Day",
|
||||
LocalizableKey = "EnvironmentDay",
|
||||
CountryCode = null,
|
||||
Holidays = [NotableHolidayType.Observance]
|
||||
};
|
||||
globalDays.Add(environmentDay);
|
||||
|
||||
// Halloween - October 31
|
||||
var halloween = new NotableDay
|
||||
{
|
||||
Date = Instant.FromDateTimeUtc(new DateTime(year, 10, 31, 0, 0, 0, DateTimeKind.Utc)),
|
||||
LocalName = "Halloween",
|
||||
GlobalName = "Halloween",
|
||||
LocalizableKey = "Halloween",
|
||||
CountryCode = null,
|
||||
Holidays = [NotableHolidayType.Observance]
|
||||
};
|
||||
globalDays.Add(halloween);
|
||||
|
||||
return globalDays;
|
||||
}
|
||||
|
||||
public async Task<NotableDay?> GetNextHoliday(string regionCode)
|
||||
{
|
||||
var currentDate = SystemClock.Instance.GetCurrentInstant();
|
||||
@@ -52,4 +163,37 @@ public class NotableDaysService(ICacheService cache)
|
||||
|
||||
return nextHoliday;
|
||||
}
|
||||
|
||||
public async Task<NotableDay?> GetCurrentHoliday(string regionCode)
|
||||
{
|
||||
var currentDate = SystemClock.Instance.GetCurrentInstant();
|
||||
var currentYear = currentDate.InUtc().Year;
|
||||
|
||||
var currentYearHolidays = await GetNotableDays(currentYear, regionCode);
|
||||
|
||||
// Find the holiday that is today
|
||||
var todayHoliday = currentYearHolidays
|
||||
.FirstOrDefault(day => day.Date.InUtc().Date == currentDate.InUtc().Date);
|
||||
|
||||
return todayHoliday;
|
||||
}
|
||||
|
||||
public async Task<List<NotableDay>> GetCurrentAndNextHoliday(string regionCode)
|
||||
{
|
||||
var result = new List<NotableDay>();
|
||||
|
||||
var current = await GetCurrentHoliday(regionCode);
|
||||
if (current != null)
|
||||
{
|
||||
result.Add(current);
|
||||
}
|
||||
|
||||
var next = await GetNextHoliday(regionCode);
|
||||
if (next != null && (current == null || !next.Date.Equals(current.Date)))
|
||||
{
|
||||
result.Add(next);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ public class RelationshipController(AppDatabase db, RelationshipService rels) :
|
||||
var userId = currentUser.Id;
|
||||
|
||||
var query = db.AccountRelationships.AsQueryable()
|
||||
.OrderByDescending(r => r.CreatedAt)
|
||||
.Where(r => r.RelatedId == userId);
|
||||
var totalCount = await query.CountAsync();
|
||||
var relationships = await query
|
||||
@@ -35,8 +36,9 @@ public class RelationshipController(AppDatabase db, RelationshipService rels) :
|
||||
.Where(r => r.AccountId == userId)
|
||||
.ToDictionaryAsync(r => r.RelatedId);
|
||||
foreach (var relationship in relationships)
|
||||
if (statuses.TryGetValue(relationship.RelatedId, out var status))
|
||||
relationship.Status = status.Status;
|
||||
relationship.Status = statuses.TryGetValue(relationship.AccountId, out var status)
|
||||
? status.Status
|
||||
: RelationshipStatus.Pending;
|
||||
|
||||
Response.Headers["X-Total"] = totalCount.ToString();
|
||||
|
||||
|
||||
@@ -17,12 +17,18 @@ public class RelationshipService(
|
||||
{
|
||||
private const string UserFriendsCacheKeyPrefix = "accounts:friends:";
|
||||
private const string UserBlockedCacheKeyPrefix = "accounts:blocked:";
|
||||
private static readonly TimeSpan CacheExpiration = TimeSpan.FromHours(1);
|
||||
|
||||
public async Task<bool> HasExistingRelationship(Guid accountId, Guid relatedId)
|
||||
{
|
||||
if (accountId == Guid.Empty || relatedId == Guid.Empty)
|
||||
throw new ArgumentException("Account IDs cannot be empty.");
|
||||
if (accountId == relatedId)
|
||||
return false; // Prevent self-relationships
|
||||
|
||||
var count = await db.AccountRelationships
|
||||
.Where(r => (r.AccountId == accountId && r.RelatedId == relatedId) ||
|
||||
(r.AccountId == relatedId && r.AccountId == accountId))
|
||||
(r.AccountId == relatedId && r.RelatedId == accountId))
|
||||
.CountAsync();
|
||||
return count > 0;
|
||||
}
|
||||
@@ -34,6 +40,9 @@ public class RelationshipService(
|
||||
bool ignoreExpired = false
|
||||
)
|
||||
{
|
||||
if (accountId == Guid.Empty || relatedId == Guid.Empty)
|
||||
throw new ArgumentException("Account IDs cannot be empty.");
|
||||
|
||||
var now = Instant.FromDateTimeUtc(DateTime.UtcNow);
|
||||
var queries = db.AccountRelationships.AsQueryable()
|
||||
.Where(r => r.AccountId == accountId && r.RelatedId == relatedId);
|
||||
@@ -61,7 +70,7 @@ public class RelationshipService(
|
||||
db.AccountRelationships.Add(relationship);
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
await PurgeRelationshipCache(sender.Id, target.Id);
|
||||
await PurgeRelationshipCache(sender.Id, target.Id, status);
|
||||
|
||||
return relationship;
|
||||
}
|
||||
@@ -80,7 +89,7 @@ public class RelationshipService(
|
||||
db.Remove(relationship);
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
await PurgeRelationshipCache(sender.Id, target.Id);
|
||||
await PurgeRelationshipCache(sender.Id, target.Id, RelationshipStatus.Blocked);
|
||||
|
||||
return relationship;
|
||||
}
|
||||
@@ -114,19 +123,24 @@ public class RelationshipService(
|
||||
}
|
||||
});
|
||||
|
||||
await PurgeRelationshipCache(sender.Id, target.Id, RelationshipStatus.Pending);
|
||||
|
||||
return relationship;
|
||||
}
|
||||
|
||||
public async Task DeleteFriendRequest(Guid accountId, Guid relatedId)
|
||||
{
|
||||
var relationship = await GetRelationship(accountId, relatedId, RelationshipStatus.Pending);
|
||||
if (relationship is null) throw new ArgumentException("Friend request was not found.");
|
||||
if (accountId == Guid.Empty || relatedId == Guid.Empty)
|
||||
throw new ArgumentException("Account IDs cannot be empty.");
|
||||
|
||||
await db.AccountRelationships
|
||||
var affectedRows = await db.AccountRelationships
|
||||
.Where(r => r.AccountId == accountId && r.RelatedId == relatedId && r.Status == RelationshipStatus.Pending)
|
||||
.ExecuteDeleteAsync();
|
||||
|
||||
await PurgeRelationshipCache(relationship.AccountId, relationship.RelatedId);
|
||||
if (affectedRows == 0)
|
||||
throw new ArgumentException("Friend request was not found.");
|
||||
|
||||
await PurgeRelationshipCache(accountId, relatedId, RelationshipStatus.Pending);
|
||||
}
|
||||
|
||||
public async Task<SnAccountRelationship> AcceptFriendRelationship(
|
||||
@@ -155,7 +169,7 @@ public class RelationshipService(
|
||||
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
await PurgeRelationshipCache(relationship.AccountId, relationship.RelatedId);
|
||||
await PurgeRelationshipCache(relationship.AccountId, relationship.RelatedId, RelationshipStatus.Friends, status);
|
||||
|
||||
return relationshipBackward;
|
||||
}
|
||||
@@ -165,11 +179,12 @@ public class RelationshipService(
|
||||
var relationship = await GetRelationship(accountId, relatedId);
|
||||
if (relationship is null) throw new ArgumentException("There is no relationship between you and the user.");
|
||||
if (relationship.Status == status) return relationship;
|
||||
var oldStatus = relationship.Status;
|
||||
relationship.Status = status;
|
||||
db.Update(relationship);
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
await PurgeRelationshipCache(accountId, relatedId);
|
||||
await PurgeRelationshipCache(accountId, relatedId, oldStatus, status);
|
||||
|
||||
return relationship;
|
||||
}
|
||||
@@ -181,21 +196,7 @@ public class RelationshipService(
|
||||
|
||||
public async Task<List<Guid>> ListAccountFriends(Guid accountId)
|
||||
{
|
||||
var cacheKey = $"{UserFriendsCacheKeyPrefix}{accountId}";
|
||||
var friends = await cache.GetAsync<List<Guid>>(cacheKey);
|
||||
|
||||
if (friends == null)
|
||||
{
|
||||
friends = await db.AccountRelationships
|
||||
.Where(r => r.RelatedId == accountId)
|
||||
.Where(r => r.Status == RelationshipStatus.Friends)
|
||||
.Select(r => r.AccountId)
|
||||
.ToListAsync();
|
||||
|
||||
await cache.SetAsync(cacheKey, friends, TimeSpan.FromHours(1));
|
||||
}
|
||||
|
||||
return friends ?? [];
|
||||
return await GetCachedRelationships(accountId, RelationshipStatus.Friends, UserFriendsCacheKeyPrefix);
|
||||
}
|
||||
|
||||
public async Task<List<Guid>> ListAccountBlocked(SnAccount account)
|
||||
@@ -205,21 +206,7 @@ public class RelationshipService(
|
||||
|
||||
public async Task<List<Guid>> ListAccountBlocked(Guid accountId)
|
||||
{
|
||||
var cacheKey = $"{UserBlockedCacheKeyPrefix}{accountId}";
|
||||
var blocked = await cache.GetAsync<List<Guid>>(cacheKey);
|
||||
|
||||
if (blocked == null)
|
||||
{
|
||||
blocked = await db.AccountRelationships
|
||||
.Where(r => r.RelatedId == accountId)
|
||||
.Where(r => r.Status == RelationshipStatus.Blocked)
|
||||
.Select(r => r.AccountId)
|
||||
.ToListAsync();
|
||||
|
||||
await cache.SetAsync(cacheKey, blocked, TimeSpan.FromHours(1));
|
||||
}
|
||||
|
||||
return blocked ?? [];
|
||||
return await GetCachedRelationships(accountId, RelationshipStatus.Blocked, UserBlockedCacheKeyPrefix);
|
||||
}
|
||||
|
||||
public async Task<bool> HasRelationshipWithStatus(Guid accountId, Guid relatedId,
|
||||
@@ -229,11 +216,59 @@ public class RelationshipService(
|
||||
return relationship is not null;
|
||||
}
|
||||
|
||||
private async Task PurgeRelationshipCache(Guid accountId, Guid relatedId)
|
||||
private async Task<List<Guid>> GetCachedRelationships(Guid accountId, RelationshipStatus status, string cachePrefix)
|
||||
{
|
||||
await cache.RemoveAsync($"{UserFriendsCacheKeyPrefix}{accountId}");
|
||||
await cache.RemoveAsync($"{UserFriendsCacheKeyPrefix}{relatedId}");
|
||||
await cache.RemoveAsync($"{UserBlockedCacheKeyPrefix}{accountId}");
|
||||
await cache.RemoveAsync($"{UserBlockedCacheKeyPrefix}{relatedId}");
|
||||
if (accountId == Guid.Empty)
|
||||
throw new ArgumentException("Account ID cannot be empty.");
|
||||
|
||||
var cacheKey = $"{cachePrefix}{accountId}";
|
||||
var relationships = await cache.GetAsync<List<Guid>>(cacheKey);
|
||||
|
||||
if (relationships != null) return relationships;
|
||||
var now = Instant.FromDateTimeUtc(DateTime.UtcNow);
|
||||
var query = db.AccountRelationships
|
||||
.Where(r => r.RelatedId == accountId)
|
||||
.Where(r => r.Status == status)
|
||||
.Where(r => r.ExpiredAt == null || r.ExpiredAt > now)
|
||||
.Select(r => r.AccountId);
|
||||
|
||||
if (status == RelationshipStatus.Friends)
|
||||
{
|
||||
var usersBlockedByMe = db.AccountRelationships
|
||||
.Where(r => r.AccountId == accountId && r.Status == RelationshipStatus.Blocked)
|
||||
.Select(r => r.RelatedId);
|
||||
query = query.Except(usersBlockedByMe);
|
||||
}
|
||||
|
||||
relationships = await query.ToListAsync();
|
||||
|
||||
await cache.SetAsync(cacheKey, relationships, CacheExpiration);
|
||||
|
||||
return relationships;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task PurgeRelationshipCache(Guid accountId, Guid relatedId, params RelationshipStatus[] statuses)
|
||||
{
|
||||
if (statuses.Length == 0)
|
||||
{
|
||||
statuses = Enum.GetValues<RelationshipStatus>();
|
||||
}
|
||||
|
||||
var keysToRemove = new List<string>();
|
||||
|
||||
if (statuses.Contains(RelationshipStatus.Friends) || statuses.Contains(RelationshipStatus.Pending))
|
||||
{
|
||||
keysToRemove.Add($"{UserFriendsCacheKeyPrefix}{accountId}");
|
||||
keysToRemove.Add($"{UserFriendsCacheKeyPrefix}{relatedId}");
|
||||
}
|
||||
|
||||
if (statuses.Contains(RelationshipStatus.Blocked))
|
||||
{
|
||||
keysToRemove.Add($"{UserBlockedCacheKeyPrefix}{accountId}");
|
||||
keysToRemove.Add($"{UserBlockedCacheKeyPrefix}{relatedId}");
|
||||
}
|
||||
|
||||
var removeTasks = keysToRemove.Select(key => cache.RemoveAsync(key));
|
||||
await Task.WhenAll(removeTasks);
|
||||
}
|
||||
}
|
||||
|
||||
134
DysonNetwork.Pass/Affiliation/AffiliationSpellController.cs
Normal file
134
DysonNetwork.Pass/Affiliation/AffiliationSpellController.cs
Normal file
@@ -0,0 +1,134 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using DysonNetwork.Shared.Models;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace DysonNetwork.Pass.Affiliation;
|
||||
|
||||
[ApiController]
|
||||
[Route("/api/affiliations")]
|
||||
public class AffiliationSpellController(AppDatabase db, AffiliationSpellService ars) : ControllerBase
|
||||
{
|
||||
public class CreateAffiliationSpellRequest
|
||||
{
|
||||
[MaxLength(1024)] public string? Spell { get; set; }
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Authorize]
|
||||
public async Task<ActionResult<SnAffiliationSpell>> CreateSpell([FromBody] CreateAffiliationSpellRequest request)
|
||||
{
|
||||
if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser) return Unauthorized();
|
||||
|
||||
try
|
||||
{
|
||||
var spell = await ars.CreateAffiliationSpell(currentUser.Id, request.Spell);
|
||||
return Ok(spell);
|
||||
}
|
||||
catch (InvalidOperationException e)
|
||||
{
|
||||
return BadRequest(e.Message);
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Authorize]
|
||||
public async Task<ActionResult<List<SnAffiliationSpell>>> ListCreatedSpells(
|
||||
[FromQuery(Name = "order")] string orderBy = "date",
|
||||
[FromQuery(Name = "desc")] bool orderDesc = false,
|
||||
[FromQuery] int take = 10,
|
||||
[FromQuery] int offset = 0
|
||||
)
|
||||
{
|
||||
if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser) return Unauthorized();
|
||||
|
||||
var queryable = db.AffiliationSpells
|
||||
.Where(s => s.AccountId == currentUser.Id)
|
||||
.AsQueryable();
|
||||
|
||||
queryable = orderBy switch
|
||||
{
|
||||
"usage" => orderDesc
|
||||
? queryable.OrderByDescending(q => q.Results.Count)
|
||||
: queryable.OrderBy(q => q.Results.Count),
|
||||
_ => orderDesc
|
||||
? queryable.OrderByDescending(q => q.CreatedAt)
|
||||
: queryable.OrderBy(q => q.CreatedAt)
|
||||
};
|
||||
|
||||
var totalCount = queryable.Count();
|
||||
Response.Headers["X-Total"] = totalCount.ToString();
|
||||
|
||||
var spells = await queryable
|
||||
.Skip(offset)
|
||||
.Take(take)
|
||||
.ToListAsync();
|
||||
return Ok(spells);
|
||||
}
|
||||
|
||||
[HttpGet("{id:guid}")]
|
||||
[Authorize]
|
||||
public async Task<ActionResult<SnAffiliationSpell>> GetSpell([FromRoute] Guid id)
|
||||
{
|
||||
if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser) return Unauthorized();
|
||||
|
||||
var spell = await db.AffiliationSpells
|
||||
.Where(s => s.AccountId == currentUser.Id)
|
||||
.Where(s => s.Id == id)
|
||||
.FirstOrDefaultAsync();
|
||||
if (spell is null) return NotFound();
|
||||
|
||||
return Ok(spell);
|
||||
}
|
||||
|
||||
[HttpGet("{id:guid}/results")]
|
||||
[Authorize]
|
||||
public async Task<ActionResult<List<SnAffiliationResult>>> ListResults(
|
||||
[FromRoute] Guid id,
|
||||
[FromQuery(Name = "desc")] bool orderDesc = false,
|
||||
[FromQuery] int take = 10,
|
||||
[FromQuery] int offset = 0
|
||||
)
|
||||
{
|
||||
if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser) return Unauthorized();
|
||||
|
||||
var queryable = db.AffiliationResults
|
||||
.Include(r => r.Spell)
|
||||
.Where(r => r.Spell.AccountId == currentUser.Id)
|
||||
.Where(r => r.SpellId == id)
|
||||
.AsQueryable();
|
||||
|
||||
// Order by creation date
|
||||
queryable = orderDesc
|
||||
? queryable.OrderByDescending(r => r.CreatedAt)
|
||||
: queryable.OrderBy(r => r.CreatedAt);
|
||||
|
||||
var totalCount = queryable.Count();
|
||||
Response.Headers["X-Total"] = totalCount.ToString();
|
||||
|
||||
var results = await queryable
|
||||
.Skip(offset)
|
||||
.Take(take)
|
||||
.ToListAsync();
|
||||
return Ok(results);
|
||||
}
|
||||
|
||||
[HttpDelete("{id:guid}")]
|
||||
[Authorize]
|
||||
public async Task<ActionResult> DeleteSpell([FromRoute] Guid id)
|
||||
{
|
||||
if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser) return Unauthorized();
|
||||
|
||||
var spell = await db.AffiliationSpells
|
||||
.Where(s => s.AccountId == currentUser.Id)
|
||||
.Where(s => s.Id == id)
|
||||
.FirstOrDefaultAsync();
|
||||
if (spell is null) return NotFound();
|
||||
|
||||
db.AffiliationSpells.Remove(spell);
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
return Ok();
|
||||
}
|
||||
}
|
||||
62
DysonNetwork.Pass/Affiliation/AffiliationSpellService.cs
Normal file
62
DysonNetwork.Pass/Affiliation/AffiliationSpellService.cs
Normal file
@@ -0,0 +1,62 @@
|
||||
using System.Security.Cryptography;
|
||||
using DysonNetwork.Shared.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace DysonNetwork.Pass.Affiliation;
|
||||
|
||||
public class AffiliationSpellService(AppDatabase db)
|
||||
{
|
||||
public async Task<SnAffiliationSpell> CreateAffiliationSpell(Guid accountId, string? spellWord)
|
||||
{
|
||||
spellWord ??= _GenerateRandomString(8);
|
||||
if (await CheckAffiliationSpellHasTaken(spellWord))
|
||||
throw new InvalidOperationException("The spell has been taken.");
|
||||
|
||||
var spell = new SnAffiliationSpell
|
||||
{
|
||||
AccountId = accountId,
|
||||
Spell = spellWord
|
||||
};
|
||||
|
||||
db.AffiliationSpells.Add(spell);
|
||||
await db.SaveChangesAsync();
|
||||
return spell;
|
||||
}
|
||||
|
||||
public async Task<SnAffiliationResult> CreateAffiliationResult(string spellWord, string resourceId)
|
||||
{
|
||||
var spell =
|
||||
await db.AffiliationSpells.FirstOrDefaultAsync(a => a.Spell == spellWord);
|
||||
if (spell is null) throw new InvalidOperationException("The spell was not found.");
|
||||
|
||||
var result = new SnAffiliationResult
|
||||
{
|
||||
Spell = spell,
|
||||
ResourceIdentifier = resourceId
|
||||
};
|
||||
db.AffiliationResults.Add(result);
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<bool> CheckAffiliationSpellHasTaken(string spellWord)
|
||||
{
|
||||
return await db.AffiliationSpells.AnyAsync(s => s.Spell == spellWord);
|
||||
}
|
||||
|
||||
private static string _GenerateRandomString(int length)
|
||||
{
|
||||
const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
||||
var result = new char[length];
|
||||
using var rng = RandomNumberGenerator.Create();
|
||||
for (var i = 0; i < length; i++)
|
||||
{
|
||||
var bytes = new byte[1];
|
||||
rng.GetBytes(bytes);
|
||||
result[i] = chars[bytes[0] % chars.Length];
|
||||
}
|
||||
|
||||
return new string(result);
|
||||
}
|
||||
}
|
||||
@@ -61,6 +61,11 @@ public class AppDatabase(
|
||||
public DbSet<SnLottery> Lotteries { get; set; } = null!;
|
||||
public DbSet<SnLotteryRecord> LotteryRecords { get; set; } = null!;
|
||||
|
||||
public DbSet<SnAffiliationSpell> AffiliationSpells { get; set; } = null!;
|
||||
public DbSet<SnAffiliationResult> AffiliationResults { get; set; } = null!;
|
||||
|
||||
public DbSet<SnRewindPoint> RewindPoints { get; set; }
|
||||
|
||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
||||
{
|
||||
optionsBuilder.UseNpgsql(
|
||||
@@ -100,7 +105,7 @@ public class AppDatabase(
|
||||
"stickers.packs.create",
|
||||
"stickers.create"
|
||||
}.Select(permission =>
|
||||
PermissionService.NewPermissionNode("group:default", "global", permission, true))
|
||||
PermissionService.NewPermissionNode("group:default", permission, true))
|
||||
.ToList()
|
||||
});
|
||||
await context.SaveChangesAsync(cancellationToken);
|
||||
|
||||
@@ -70,7 +70,7 @@ public class DysonTokenAuthHandler(
|
||||
};
|
||||
|
||||
// Add scopes as claims
|
||||
session.Challenge?.Scopes.ForEach(scope => claims.Add(new Claim("scope", scope)));
|
||||
session.Scopes.ForEach(scope => claims.Add(new Claim("scope", scope)));
|
||||
|
||||
// Add superuser claim if applicable
|
||||
if (session.Account.IsSuperuser)
|
||||
@@ -117,16 +117,17 @@ public class DysonTokenAuthHandler(
|
||||
{
|
||||
if (authHeader.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var token = authHeader["Bearer ".Length..].Trim();
|
||||
var parts = token.Split('.');
|
||||
var tokenText = authHeader["Bearer ".Length..].Trim();
|
||||
var parts = tokenText.Split('.');
|
||||
|
||||
return new TokenInfo
|
||||
{
|
||||
Token = token,
|
||||
Token = tokenText,
|
||||
Type = parts.Length == 3 ? TokenType.OidcKey : TokenType.AuthKey
|
||||
};
|
||||
}
|
||||
else if (authHeader.StartsWith("AtField ", StringComparison.OrdinalIgnoreCase))
|
||||
|
||||
if (authHeader.StartsWith("AtField ", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return new TokenInfo
|
||||
{
|
||||
@@ -134,7 +135,8 @@ public class DysonTokenAuthHandler(
|
||||
Type = TokenType.AuthKey
|
||||
};
|
||||
}
|
||||
else if (authHeader.StartsWith("AkField ", StringComparison.OrdinalIgnoreCase))
|
||||
|
||||
if (authHeader.StartsWith("AkField ", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return new TokenInfo
|
||||
{
|
||||
|
||||
@@ -3,7 +3,7 @@ using Microsoft.AspNetCore.Mvc;
|
||||
using NodaTime;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using DysonNetwork.Pass.Localization;
|
||||
using DysonNetwork.Shared.GeoIp;
|
||||
using DysonNetwork.Shared.Geometry;
|
||||
using DysonNetwork.Shared.Proto;
|
||||
using Microsoft.Extensions.Localization;
|
||||
using AccountService = DysonNetwork.Pass.Account.AccountService;
|
||||
@@ -18,7 +18,7 @@ public class AuthController(
|
||||
AppDatabase db,
|
||||
AccountService accounts,
|
||||
AuthService auth,
|
||||
GeoIpService geo,
|
||||
GeoService geo,
|
||||
ActionLogService als,
|
||||
RingService.RingServiceClient pusher,
|
||||
IConfiguration configuration,
|
||||
@@ -30,12 +30,12 @@ public class AuthController(
|
||||
|
||||
public class ChallengeRequest
|
||||
{
|
||||
[Required] public ClientPlatform Platform { get; set; }
|
||||
[Required] public Shared.Models.ClientPlatform Platform { get; set; }
|
||||
[Required] [MaxLength(256)] public string Account { get; set; } = null!;
|
||||
[Required] [MaxLength(512)] public string DeviceId { get; set; } = null!;
|
||||
[MaxLength(1024)] public string? DeviceName { get; set; }
|
||||
public List<string> Audiences { get; set; } = new();
|
||||
public List<string> Scopes { get; set; } = new();
|
||||
public List<string> Audiences { get; set; } = [];
|
||||
public List<string> Scopes { get; set; } = [];
|
||||
}
|
||||
|
||||
[HttpPost("challenge")]
|
||||
@@ -61,9 +61,6 @@ public class AuthController(
|
||||
|
||||
request.DeviceName ??= userAgent;
|
||||
|
||||
var device =
|
||||
await auth.GetOrCreateDeviceAsync(account.Id, request.DeviceId, request.DeviceName, request.Platform);
|
||||
|
||||
// Trying to pick up challenges from the same IP address and user agent
|
||||
var existingChallenge = await db.AuthChallenges
|
||||
.Where(e => e.AccountId == account.Id)
|
||||
@@ -71,15 +68,9 @@ public class AuthController(
|
||||
.Where(e => e.UserAgent == userAgent)
|
||||
.Where(e => e.StepRemain > 0)
|
||||
.Where(e => e.ExpiredAt != null && now < e.ExpiredAt)
|
||||
.Where(e => e.Type == Shared.Models.ChallengeType.Login)
|
||||
.Where(e => e.ClientId == device.Id)
|
||||
.Where(e => e.DeviceId == request.DeviceId)
|
||||
.FirstOrDefaultAsync();
|
||||
if (existingChallenge is not null)
|
||||
{
|
||||
var existingSession = await db.AuthSessions.Where(e => e.ChallengeId == existingChallenge.Id)
|
||||
.FirstOrDefaultAsync();
|
||||
if (existingSession is null) return existingChallenge;
|
||||
}
|
||||
if (existingChallenge is not null) return existingChallenge;
|
||||
|
||||
var challenge = new SnAuthChallenge
|
||||
{
|
||||
@@ -90,7 +81,9 @@ public class AuthController(
|
||||
IpAddress = ipAddress,
|
||||
UserAgent = userAgent,
|
||||
Location = geo.GetPointFromIp(ipAddress),
|
||||
ClientId = device.Id,
|
||||
DeviceId = request.DeviceId,
|
||||
DeviceName = request.DeviceName,
|
||||
Platform = request.Platform,
|
||||
AccountId = account.Id
|
||||
}.Normalize();
|
||||
|
||||
@@ -112,14 +105,11 @@ public class AuthController(
|
||||
.ThenInclude(e => e.Profile)
|
||||
.FirstOrDefaultAsync(e => e.Id == id);
|
||||
|
||||
if (challenge is null)
|
||||
{
|
||||
logger.LogWarning("GetChallenge: challenge not found (challengeId={ChallengeId}, ip={IpAddress})",
|
||||
id, HttpContext.Connection.RemoteIpAddress?.ToString());
|
||||
return NotFound("Auth challenge was not found.");
|
||||
}
|
||||
if (challenge is not null) return challenge;
|
||||
logger.LogWarning("GetChallenge: challenge not found (challengeId={ChallengeId}, ip={IpAddress})",
|
||||
id, HttpContext.Connection.RemoteIpAddress?.ToString());
|
||||
return NotFound("Auth challenge was not found.");
|
||||
|
||||
return challenge;
|
||||
}
|
||||
|
||||
[HttpGet("challenge/{id:guid}/factors")]
|
||||
@@ -176,7 +166,6 @@ public class AuthController(
|
||||
{
|
||||
var challenge = await db.AuthChallenges
|
||||
.Include(e => e.Account)
|
||||
.Include(authChallenge => authChallenge.Client)
|
||||
.FirstOrDefaultAsync(e => e.Id == id);
|
||||
if (challenge is null) return NotFound("Auth challenge was not found.");
|
||||
|
||||
@@ -218,7 +207,7 @@ public class AuthController(
|
||||
throw new ArgumentException("Invalid password.");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
catch (Exception)
|
||||
{
|
||||
challenge.FailedAttempts++;
|
||||
db.Update(challenge);
|
||||
@@ -231,8 +220,11 @@ public class AuthController(
|
||||
);
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
logger.LogWarning("DoChallenge: authentication failure (challengeId={ChallengeId}, factorId={FactorId}, accountId={AccountId}, failedAttempts={FailedAttempts}, factorType={FactorType}, ip={IpAddress}, uaLength={UaLength})",
|
||||
challenge.Id, factor.Id, challenge.AccountId, challenge.FailedAttempts, factor.Type, HttpContext.Connection.RemoteIpAddress?.ToString(), (HttpContext.Request.Headers.UserAgent.ToString() ?? "").Length);
|
||||
logger.LogWarning(
|
||||
"DoChallenge: authentication failure (challengeId={ChallengeId}, factorId={FactorId}, accountId={AccountId}, failedAttempts={FailedAttempts}, factorType={FactorType}, ip={IpAddress}, uaLength={UaLength})",
|
||||
challenge.Id, factor.Id, challenge.AccountId, challenge.FailedAttempts, factor.Type,
|
||||
HttpContext.Connection.RemoteIpAddress?.ToString(),
|
||||
HttpContext.Request.Headers.UserAgent.ToString().Length);
|
||||
|
||||
return BadRequest("Invalid password.");
|
||||
}
|
||||
@@ -242,11 +234,11 @@ public class AuthController(
|
||||
AccountService.SetCultureInfo(challenge.Account);
|
||||
await pusher.SendPushNotificationToUserAsync(new SendPushNotificationToUserRequest
|
||||
{
|
||||
Notification = new PushNotification()
|
||||
Notification = new PushNotification
|
||||
{
|
||||
Topic = "auth.login",
|
||||
Title = localizer["NewLoginTitle"],
|
||||
Body = localizer["NewLoginBody", challenge.Client?.DeviceName ?? "unknown",
|
||||
Body = localizer["NewLoginBody", challenge.DeviceName ?? "unknown",
|
||||
challenge.IpAddress ?? "unknown"],
|
||||
IsSavable = true
|
||||
},
|
||||
@@ -277,6 +269,14 @@ public class AuthController(
|
||||
public string Token { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
public class NewSessionRequest
|
||||
{
|
||||
[Required] [MaxLength(512)] public string DeviceId { get; set; } = null!;
|
||||
[MaxLength(1024)] public string? DeviceName { get; set; }
|
||||
[Required] public Shared.Models.ClientPlatform Platform { get; set; }
|
||||
public Instant? ExpiredAt { get; set; }
|
||||
}
|
||||
|
||||
[HttpPost("token")]
|
||||
public async Task<ActionResult<TokenExchangeResponse>> ExchangeToken([FromBody] TokenExchangeRequest request)
|
||||
{
|
||||
@@ -327,4 +327,35 @@ public class AuthController(
|
||||
});
|
||||
return Ok();
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("login/session")]
|
||||
[Microsoft.AspNetCore.Authorization.Authorize] // Use full namespace to avoid ambiguity with DysonNetwork.Pass.Permission.Authorize
|
||||
public async Task<ActionResult<TokenExchangeResponse>> LoginFromSession([FromBody] NewSessionRequest request)
|
||||
{
|
||||
if (HttpContext.Items["CurrentUser"] is not SnAccount ||
|
||||
HttpContext.Items["CurrentSession"] is not SnAuthSession currentSession)
|
||||
return Unauthorized();
|
||||
|
||||
var newSession = await auth.CreateSessionFromParentAsync(
|
||||
currentSession,
|
||||
request.DeviceId,
|
||||
request.DeviceName,
|
||||
request.Platform,
|
||||
request.ExpiredAt
|
||||
);
|
||||
|
||||
var tk = auth.CreateToken(newSession);
|
||||
|
||||
// Set cookie using HttpContext, similar to CreateSessionAndIssueToken
|
||||
HttpContext.Response.Cookies.Append(AuthConstants.CookieTokenName, tk, new CookieOptions
|
||||
{
|
||||
HttpOnly = true,
|
||||
Secure = true,
|
||||
SameSite = SameSiteMode.Lax,
|
||||
Domain = _cookieDomain,
|
||||
Expires = request.ExpiredAt?.ToDateTimeOffset() ?? DateTime.UtcNow.AddYears(20)
|
||||
});
|
||||
|
||||
return Ok(new TokenExchangeResponse { Token = tk });
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,8 @@ using System.Security.Cryptography;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using DysonNetwork.Shared.Cache;
|
||||
using DysonNetwork.Shared.Data;
|
||||
using DysonNetwork.Shared.Geometry;
|
||||
using DysonNetwork.Shared.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NodaTime;
|
||||
@@ -13,7 +15,8 @@ public class AuthService(
|
||||
IConfiguration config,
|
||||
IHttpClientFactory httpClientFactory,
|
||||
IHttpContextAccessor httpContextAccessor,
|
||||
ICacheService cache
|
||||
ICacheService cache,
|
||||
GeoService geo
|
||||
)
|
||||
{
|
||||
private HttpContext HttpContext => httpContextAccessor.HttpContext!;
|
||||
@@ -30,7 +33,7 @@ public class AuthService(
|
||||
{
|
||||
// 1) Find out how many authentication factors the account has enabled.
|
||||
var enabledFactors = await db.AccountAuthFactors
|
||||
.Where(f => f.AccountId == account.Id)
|
||||
.Where(f => f.AccountId == account.Id && f.Type != AccountAuthFactorType.PinCode)
|
||||
.Where(f => f.EnabledAt != null)
|
||||
.ToListAsync();
|
||||
var maxSteps = enabledFactors.Count;
|
||||
@@ -41,13 +44,18 @@ public class AuthService(
|
||||
|
||||
// 2) Get login context from recent sessions
|
||||
var recentSessions = await db.AuthSessions
|
||||
.Include(s => s.Challenge)
|
||||
.Where(s => s.AccountId == account.Id)
|
||||
.Where(s => s.LastGrantedAt != null)
|
||||
.OrderByDescending(s => s.LastGrantedAt)
|
||||
.Take(10)
|
||||
.ToListAsync();
|
||||
|
||||
var recentChallengeIds =
|
||||
recentSessions
|
||||
.Where(s => s.ChallengeId != null)
|
||||
.Select(s => s.ChallengeId!.Value).ToList();
|
||||
var recentChallenges = await db.AuthChallenges.Where(c => recentChallengeIds.Contains(c.Id)).ToListAsync();
|
||||
|
||||
var ipAddress = request.HttpContext.Connection.RemoteIpAddress?.ToString();
|
||||
var userAgent = request.Headers.UserAgent.ToString();
|
||||
|
||||
@@ -59,14 +67,14 @@ public class AuthService(
|
||||
else
|
||||
{
|
||||
// Check if IP has been used before
|
||||
var ipPreviouslyUsed = recentSessions.Any(s => s.Challenge?.IpAddress == ipAddress);
|
||||
var ipPreviouslyUsed = recentChallenges.Any(c => c.IpAddress == ipAddress);
|
||||
if (!ipPreviouslyUsed)
|
||||
{
|
||||
riskScore += 8;
|
||||
}
|
||||
|
||||
// Check geographical distance for last known location
|
||||
var lastKnownIp = recentSessions.FirstOrDefault(s => !string.IsNullOrWhiteSpace(s.Challenge?.IpAddress))?.Challenge?.IpAddress;
|
||||
var lastKnownIp = recentChallenges.FirstOrDefault(c => !string.IsNullOrWhiteSpace(c.IpAddress))?.IpAddress;
|
||||
if (!string.IsNullOrWhiteSpace(lastKnownIp) && lastKnownIp != ipAddress)
|
||||
{
|
||||
riskScore += 6;
|
||||
@@ -80,9 +88,9 @@ public class AuthService(
|
||||
}
|
||||
else
|
||||
{
|
||||
var uaPreviouslyUsed = recentSessions.Any(s =>
|
||||
!string.IsNullOrWhiteSpace(s.Challenge?.UserAgent) &&
|
||||
string.Equals(s.Challenge.UserAgent, userAgent, StringComparison.OrdinalIgnoreCase));
|
||||
var uaPreviouslyUsed = recentChallenges.Any(c =>
|
||||
!string.IsNullOrWhiteSpace(c.UserAgent) &&
|
||||
string.Equals(c.UserAgent, userAgent, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (!uaPreviouslyUsed)
|
||||
{
|
||||
@@ -156,7 +164,7 @@ public class AuthService(
|
||||
// 8) Device Trust Assessment
|
||||
var trustedDeviceIds = recentSessions
|
||||
.Where(s => s.CreatedAt > now.Minus(Duration.FromDays(30))) // Trust devices from last 30 days
|
||||
.Select(s => s.Challenge?.ClientId)
|
||||
.Select(s => s.ClientId)
|
||||
.Where(id => id.HasValue)
|
||||
.Distinct()
|
||||
.ToList();
|
||||
@@ -180,29 +188,28 @@ public class AuthService(
|
||||
return totalRequiredSteps;
|
||||
}
|
||||
|
||||
public async Task<SnAuthSession> CreateSessionForOidcAsync(SnAccount account, Instant time,
|
||||
Guid? customAppId = null)
|
||||
public async Task<SnAuthSession> CreateSessionForOidcAsync(
|
||||
SnAccount account,
|
||||
Instant time,
|
||||
Guid? customAppId = null,
|
||||
SnAuthSession? parentSession = null
|
||||
)
|
||||
{
|
||||
var challenge = new SnAuthChallenge
|
||||
{
|
||||
AccountId = account.Id,
|
||||
IpAddress = HttpContext.Connection.RemoteIpAddress?.ToString(),
|
||||
UserAgent = HttpContext.Request.Headers.UserAgent,
|
||||
StepRemain = 1,
|
||||
StepTotal = 1,
|
||||
Type = customAppId is not null ? ChallengeType.OAuth : ChallengeType.Oidc
|
||||
};
|
||||
|
||||
var ipAddr = HttpContext.Connection.RemoteIpAddress?.ToString();
|
||||
var geoLocation = ipAddr is not null ? geo.GetPointFromIp(ipAddr) : null;
|
||||
var session = new SnAuthSession
|
||||
{
|
||||
AccountId = account.Id,
|
||||
CreatedAt = time,
|
||||
LastGrantedAt = time,
|
||||
Challenge = challenge,
|
||||
AppId = customAppId
|
||||
IpAddress = ipAddr,
|
||||
UserAgent = HttpContext.Request.Headers.UserAgent,
|
||||
Location = geoLocation,
|
||||
AppId = customAppId,
|
||||
ParentSessionId = parentSession?.Id,
|
||||
Type = customAppId is not null ? SessionType.OAuth : SessionType.Oidc,
|
||||
};
|
||||
|
||||
db.AuthChallenges.Add(challenge);
|
||||
db.AuthSessions.Add(session);
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
@@ -216,7 +223,8 @@ public class AuthService(
|
||||
ClientPlatform platform = ClientPlatform.Unidentified
|
||||
)
|
||||
{
|
||||
var device = await db.AuthClients.FirstOrDefaultAsync(d => d.DeviceId == deviceId && d.AccountId == accountId);
|
||||
var device = await db.AuthClients
|
||||
.FirstOrDefaultAsync(d => d.DeviceId == deviceId && d.AccountId == accountId);
|
||||
if (device is not null) return device;
|
||||
device = new SnAuthClient
|
||||
{
|
||||
@@ -287,35 +295,71 @@ public class AuthService(
|
||||
|
||||
/// <summary>
|
||||
/// Immediately revoke a session by setting expiry to now and clearing from cache
|
||||
/// This provides immediate invalidation of tokens and sessions
|
||||
/// This provides immediate invalidation of tokens and sessions, including all child sessions recursively.
|
||||
/// </summary>
|
||||
/// <param name="sessionId">Session ID to revoke</param>
|
||||
/// <returns>True if session was found and revoked, false otherwise</returns>
|
||||
public async Task<bool> RevokeSessionAsync(Guid sessionId)
|
||||
{
|
||||
var session = await db.AuthSessions.FirstOrDefaultAsync(s => s.Id == sessionId);
|
||||
if (session == null)
|
||||
var sessionsToRevokeIds = new HashSet<Guid>();
|
||||
await CollectSessionsToRevoke(sessionId, sessionsToRevokeIds);
|
||||
|
||||
if (sessionsToRevokeIds.Count == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set expiry to now (immediate invalidation)
|
||||
var now = SystemClock.Instance.GetCurrentInstant();
|
||||
session.ExpiredAt = now;
|
||||
db.AuthSessions.Update(session);
|
||||
var accountIdsToClearCache = new HashSet<Guid>();
|
||||
|
||||
// Clear from cache immediately
|
||||
var cacheKey = $"{AuthCachePrefix}{session.Id}";
|
||||
await cache.RemoveAsync(cacheKey);
|
||||
// Fetch all sessions to be revoked in one go
|
||||
var sessions = await db.AuthSessions
|
||||
.Where(s => sessionsToRevokeIds.Contains(s.Id))
|
||||
.ToListAsync();
|
||||
|
||||
// Clear account-level cache groups that include this session
|
||||
await cache.RemoveAsync($"{AuthCachePrefix}{session.AccountId}");
|
||||
foreach (var session in sessions)
|
||||
{
|
||||
session.ExpiredAt = now;
|
||||
accountIdsToClearCache.Add(session.AccountId);
|
||||
|
||||
// Clear from cache immediately for each session
|
||||
await cache.RemoveAsync($"{AuthCachePrefix}{session.Id}");
|
||||
}
|
||||
|
||||
db.AuthSessions.UpdateRange(sessions);
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
// Clear account-level cache groups
|
||||
foreach (var accountId in accountIdsToClearCache)
|
||||
{
|
||||
await cache.RemoveAsync($"{AuthCachePrefix}{accountId}");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recursively collects all session IDs that need to be revoked, starting from a given session.
|
||||
/// </summary>
|
||||
/// <param name="currentSessionId">The session ID to start collecting from.</param>
|
||||
/// <param name="sessionsToRevoke">A HashSet to store the IDs of all sessions to be revoked.</param>
|
||||
private async Task CollectSessionsToRevoke(Guid currentSessionId, HashSet<Guid> sessionsToRevoke)
|
||||
{
|
||||
if (!sessionsToRevoke.Add(currentSessionId))
|
||||
return; // Already processed this session
|
||||
|
||||
// Find direct children
|
||||
var childSessions = await db.AuthSessions
|
||||
.Where(s => s.ParentSessionId == currentSessionId)
|
||||
.Select(s => s.Id)
|
||||
.ToListAsync();
|
||||
|
||||
foreach (var childId in childSessions)
|
||||
{
|
||||
await CollectSessionsToRevoke(childId, sessionsToRevoke);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Revoke all sessions for an account (logout everywhere)
|
||||
/// </summary>
|
||||
@@ -374,10 +418,12 @@ public class AuthService(
|
||||
if (challenge.StepRemain != 0)
|
||||
throw new ArgumentException("Challenge not yet completed.");
|
||||
|
||||
var hasSession = await db.AuthSessions
|
||||
.AnyAsync(e => e.ChallengeId == challenge.Id);
|
||||
if (hasSession)
|
||||
throw new ArgumentException("Session already exists for this challenge.");
|
||||
var device = await GetOrCreateDeviceAsync(
|
||||
challenge.AccountId,
|
||||
challenge.DeviceId,
|
||||
challenge.DeviceName,
|
||||
challenge.Platform
|
||||
);
|
||||
|
||||
var now = SystemClock.Instance.GetCurrentInstant();
|
||||
var session = new SnAuthSession
|
||||
@@ -385,7 +431,13 @@ public class AuthService(
|
||||
LastGrantedAt = now,
|
||||
ExpiredAt = now.Plus(Duration.FromDays(7)),
|
||||
AccountId = challenge.AccountId,
|
||||
ChallengeId = challenge.Id
|
||||
IpAddress = challenge.IpAddress,
|
||||
UserAgent = challenge.UserAgent,
|
||||
Location = challenge.Location,
|
||||
Scopes = challenge.Scopes,
|
||||
Audiences = challenge.Audiences,
|
||||
ChallengeId = challenge.Id,
|
||||
ClientId = device.Id,
|
||||
};
|
||||
|
||||
db.AuthSessions.Add(session);
|
||||
@@ -408,7 +460,7 @@ public class AuthService(
|
||||
return tk;
|
||||
}
|
||||
|
||||
private string CreateCompactToken(Guid sessionId, RSA rsa)
|
||||
private static string CreateCompactToken(Guid sessionId, RSA rsa)
|
||||
{
|
||||
// Create the payload: just the session ID
|
||||
var payloadBytes = sessionId.ToByteArray();
|
||||
@@ -499,7 +551,8 @@ public class AuthService(
|
||||
return key;
|
||||
}
|
||||
|
||||
public async Task<SnApiKey> CreateApiKey(Guid accountId, string label, Instant? expiredAt = null)
|
||||
public async Task<SnApiKey> CreateApiKey(Guid accountId, string label, Instant? expiredAt = null,
|
||||
SnAuthSession? parentSession = null)
|
||||
{
|
||||
var key = new SnApiKey
|
||||
{
|
||||
@@ -508,7 +561,8 @@ public class AuthService(
|
||||
Session = new SnAuthSession
|
||||
{
|
||||
AccountId = accountId,
|
||||
ExpiredAt = expiredAt
|
||||
ExpiredAt = expiredAt,
|
||||
ParentSessionId = parentSession?.Id
|
||||
},
|
||||
};
|
||||
|
||||
@@ -614,4 +668,47 @@ public class AuthService(
|
||||
|
||||
return Convert.FromBase64String(padded);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new session derived from an existing parent session.
|
||||
/// </summary>
|
||||
/// <param name="parentSession">The existing session from which the new session is derived.</param>
|
||||
/// <param name="deviceId">The ID of the device for the new session.</param>
|
||||
/// <param name="deviceName">The name of the device for the new session.</param>
|
||||
/// <param name="platform">The platform of the device for the new session.</param>
|
||||
/// <param name="expiredAt">Optional: The expiration time for the new session.</param>
|
||||
/// <returns>The newly created SnAuthSession.</returns>
|
||||
public async Task<SnAuthSession> CreateSessionFromParentAsync(
|
||||
SnAuthSession parentSession,
|
||||
string deviceId,
|
||||
string? deviceName,
|
||||
ClientPlatform platform,
|
||||
Instant? expiredAt = null
|
||||
)
|
||||
{
|
||||
var device = await GetOrCreateDeviceAsync(parentSession.AccountId, deviceId, deviceName, platform);
|
||||
|
||||
var ipAddress = HttpContext.Connection.RemoteIpAddress?.ToString();
|
||||
var userAgent = HttpContext.Request.Headers.UserAgent.ToString();
|
||||
var geoLocation = ipAddress is not null ? geo.GetPointFromIp(ipAddress) : null;
|
||||
|
||||
var now = SystemClock.Instance.GetCurrentInstant();
|
||||
var session = new SnAuthSession
|
||||
{
|
||||
IpAddress = ipAddress,
|
||||
UserAgent = userAgent,
|
||||
Location = geoLocation,
|
||||
AccountId = parentSession.AccountId,
|
||||
CreatedAt = now,
|
||||
LastGrantedAt = now,
|
||||
ExpiredAt = expiredAt,
|
||||
ParentSessionId = parentSession.Id,
|
||||
ClientId = device.Id,
|
||||
};
|
||||
|
||||
db.AuthSessions.Add(session);
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
return session;
|
||||
}
|
||||
}
|
||||
@@ -306,7 +306,7 @@ public class OidcProviderController(
|
||||
HttpContext.Items["CurrentSession"] is not SnAuthSession currentSession) return Unauthorized();
|
||||
|
||||
// Get requested scopes from the token
|
||||
var scopes = currentSession.Challenge?.Scopes ?? [];
|
||||
var scopes = currentSession.Scopes;
|
||||
|
||||
var userInfo = new Dictionary<string, object>
|
||||
{
|
||||
|
||||
@@ -72,7 +72,6 @@ public class OidcProviderService(
|
||||
var now = SystemClock.Instance.GetCurrentInstant();
|
||||
|
||||
var queryable = db.AuthSessions
|
||||
.Include(s => s.Challenge)
|
||||
.AsQueryable();
|
||||
if (withAccount)
|
||||
queryable = queryable
|
||||
@@ -85,8 +84,7 @@ public class OidcProviderService(
|
||||
.Where(s => s.AccountId == accountId &&
|
||||
s.AppId == clientId &&
|
||||
(s.ExpiredAt == null || s.ExpiredAt > now) &&
|
||||
s.Challenge != null &&
|
||||
s.Challenge.Type == Shared.Models.ChallengeType.OAuth)
|
||||
s.Type == Shared.Models.SessionType.OAuth)
|
||||
.OrderByDescending(s => s.CreatedAt)
|
||||
.FirstOrDefaultAsync();
|
||||
}
|
||||
@@ -511,7 +509,6 @@ public class OidcProviderService(
|
||||
{
|
||||
return await db.AuthSessions
|
||||
.Include(s => s.Account)
|
||||
.Include(s => s.Challenge)
|
||||
.FirstOrDefaultAsync(s => s.Id == sessionId);
|
||||
}
|
||||
|
||||
|
||||
@@ -342,13 +342,19 @@ public class ConnectionController(
|
||||
callbackData.State.Split('|').FirstOrDefault() :
|
||||
string.Empty;
|
||||
|
||||
var challenge = await oidcService.CreateChallengeForUserAsync(
|
||||
if (HttpContext.Items["CurrentSession"] is not SnAuthSession parentSession) parentSession = null;
|
||||
|
||||
var session = await oidcService.CreateSessionForUserAsync(
|
||||
userInfo,
|
||||
connection.Account,
|
||||
HttpContext,
|
||||
deviceId ?? string.Empty);
|
||||
deviceId ?? string.Empty,
|
||||
null,
|
||||
ClientPlatform.Web,
|
||||
parentSession);
|
||||
|
||||
var redirectUrl = QueryHelpers.AddQueryString(redirectBaseUrl, "challenge", challenge.Id.ToString());
|
||||
var token = auth.CreateToken(session);
|
||||
var redirectUrl = QueryHelpers.AddQueryString(redirectBaseUrl, "token", token);
|
||||
logger.LogInformation("OIDC login successful for user {UserId}. Redirecting to {RedirectUrl}", connection.AccountId, redirectUrl);
|
||||
return Redirect(redirectUrl);
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ public class OidcController(
|
||||
IServiceProvider serviceProvider,
|
||||
AppDatabase db,
|
||||
AccountService accounts,
|
||||
AuthService auth,
|
||||
ICacheService cache,
|
||||
ILogger<OidcController> logger
|
||||
)
|
||||
@@ -75,7 +76,7 @@ public class OidcController(
|
||||
/// Handles Apple authentication directly from mobile apps
|
||||
/// </summary>
|
||||
[HttpPost("apple/mobile")]
|
||||
public async Task<ActionResult<SnAuthChallenge>> AppleMobileLogin(
|
||||
public async Task<ActionResult<AuthController.TokenExchangeResponse>> AppleMobileLogin(
|
||||
[FromBody] AppleMobileSignInRequest request
|
||||
)
|
||||
{
|
||||
@@ -98,16 +99,21 @@ public class OidcController(
|
||||
// Find or create user account using existing logic
|
||||
var account = await FindOrCreateAccount(userInfo, "apple");
|
||||
|
||||
if (HttpContext.Items["CurrentSession"] is not SnAuthSession parentSession) parentSession = null;
|
||||
|
||||
// Create session using the OIDC service
|
||||
var challenge = await appleService.CreateChallengeForUserAsync(
|
||||
var session = await appleService.CreateSessionForUserAsync(
|
||||
userInfo,
|
||||
account,
|
||||
HttpContext,
|
||||
request.DeviceId,
|
||||
request.DeviceName
|
||||
request.DeviceName,
|
||||
ClientPlatform.Ios,
|
||||
parentSession
|
||||
);
|
||||
|
||||
return Ok(challenge);
|
||||
|
||||
var token = auth.CreateToken(session);
|
||||
return Ok(new AuthController.TokenExchangeResponse { Token = token });
|
||||
}
|
||||
catch (SecurityTokenValidationException ex)
|
||||
{
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using System;
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
@@ -250,15 +249,17 @@ public abstract class OidcService(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a challenge and session for an authenticated user
|
||||
/// Creates a session for an authenticated user
|
||||
/// Also creates or updates the account connection
|
||||
/// </summary>
|
||||
public async Task<SnAuthChallenge> CreateChallengeForUserAsync(
|
||||
public async Task<SnAuthSession> CreateSessionForUserAsync(
|
||||
OidcUserInfo userInfo,
|
||||
SnAccount account,
|
||||
HttpContext request,
|
||||
string deviceId,
|
||||
string? deviceName = null
|
||||
string? deviceName = null,
|
||||
ClientPlatform platform = ClientPlatform.Web,
|
||||
SnAuthSession? parentSession = null
|
||||
)
|
||||
{
|
||||
// Create or update the account connection
|
||||
@@ -282,28 +283,24 @@ public abstract class OidcService(
|
||||
await Db.AccountConnections.AddAsync(connection);
|
||||
}
|
||||
|
||||
// Create a challenge that's already completed
|
||||
// Create a session directly
|
||||
var now = SystemClock.Instance.GetCurrentInstant();
|
||||
var device = await auth.GetOrCreateDeviceAsync(account.Id, deviceId, deviceName, ClientPlatform.Ios);
|
||||
var challenge = new SnAuthChallenge
|
||||
{
|
||||
ExpiredAt = now.Plus(Duration.FromHours(1)),
|
||||
StepTotal = await auth.DetectChallengeRisk(request.Request, account),
|
||||
Type = ChallengeType.Oidc,
|
||||
Audiences = [ProviderName],
|
||||
Scopes = ["*"],
|
||||
AccountId = account.Id,
|
||||
ClientId = device.Id,
|
||||
IpAddress = request.Connection.RemoteIpAddress?.ToString() ?? null,
|
||||
UserAgent = request.Request.Headers.UserAgent,
|
||||
};
|
||||
challenge.StepRemain--;
|
||||
if (challenge.StepRemain < 0) challenge.StepRemain = 0;
|
||||
var device = await auth.GetOrCreateDeviceAsync(account.Id, deviceId, deviceName, platform);
|
||||
|
||||
await Db.AuthChallenges.AddAsync(challenge);
|
||||
var session = new SnAuthSession
|
||||
{
|
||||
AccountId = account.Id,
|
||||
CreatedAt = now,
|
||||
LastGrantedAt = now,
|
||||
ParentSessionId = parentSession?.Id,
|
||||
ClientId = device.Id,
|
||||
ExpiredAt = now.Plus(Duration.FromDays(30))
|
||||
};
|
||||
|
||||
await Db.AuthSessions.AddAsync(session);
|
||||
await Db.SaveChangesAsync();
|
||||
|
||||
return challenge;
|
||||
return session;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -77,7 +77,7 @@ public class TokenAuthService(
|
||||
"AuthenticateTokenAsync: success via cache (sessionId={SessionId}, accountId={AccountId}, scopes={ScopeCount}, expiresAt={ExpiresAt})",
|
||||
sessionId,
|
||||
session.AccountId,
|
||||
session.Challenge?.Scopes.Count,
|
||||
session.Scopes.Count,
|
||||
session.ExpiredAt
|
||||
);
|
||||
return (true, session, null);
|
||||
@@ -87,8 +87,7 @@ public class TokenAuthService(
|
||||
|
||||
session = await db.AuthSessions
|
||||
.AsNoTracking()
|
||||
.Include(e => e.Challenge)
|
||||
.ThenInclude(e => e.Client)
|
||||
.Include(e => e.Client)
|
||||
.Include(e => e.Account)
|
||||
.ThenInclude(e => e.Profile)
|
||||
.FirstOrDefaultAsync(s => s.Id == sessionId);
|
||||
@@ -110,11 +109,11 @@ public class TokenAuthService(
|
||||
"AuthenticateTokenAsync: DB session loaded (sessionId={SessionId}, accountId={AccountId}, clientId={ClientId}, appId={AppId}, scopes={ScopeCount}, ip={Ip}, uaLen={UaLen})",
|
||||
sessionId,
|
||||
session.AccountId,
|
||||
session.Challenge?.ClientId,
|
||||
session.ClientId,
|
||||
session.AppId,
|
||||
session.Challenge?.Scopes.Count,
|
||||
session.Challenge?.IpAddress,
|
||||
(session.Challenge?.UserAgent ?? string.Empty).Length
|
||||
session.Scopes.Count,
|
||||
session.IpAddress,
|
||||
(session.UserAgent ?? string.Empty).Length
|
||||
);
|
||||
|
||||
logger.LogDebug("AuthenticateTokenAsync: enriching account with subscription (accountId={AccountId})", session.AccountId);
|
||||
@@ -143,7 +142,7 @@ public class TokenAuthService(
|
||||
"AuthenticateTokenAsync: success via DB (sessionId={SessionId}, accountId={AccountId}, clientId={ClientId})",
|
||||
sessionId,
|
||||
session.AccountId,
|
||||
session.Challenge?.ClientId
|
||||
session.ClientId
|
||||
);
|
||||
return (true, session, null);
|
||||
}
|
||||
|
||||
@@ -132,4 +132,8 @@
|
||||
<AdditionalFiles Include="Resources\Emails\PasswordResetEmail.razor" />
|
||||
<AdditionalFiles Include="Resources\Emails\RegistrationConfirmEmail.razor" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Migrations\" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -6,16 +6,17 @@ using Quartz;
|
||||
|
||||
namespace DysonNetwork.Pass.Handlers;
|
||||
|
||||
public class ActionLogFlushHandler(IServiceProvider serviceProvider) : IFlushHandler<SnActionLog>
|
||||
public class ActionLogFlushHandler(IServiceProvider sp) : IFlushHandler<SnActionLog>
|
||||
{
|
||||
public async Task FlushAsync(IReadOnlyList<SnActionLog> items)
|
||||
{
|
||||
using var scope = serviceProvider.CreateScope();
|
||||
using var scope = sp.CreateScope();
|
||||
var db = scope.ServiceProvider.GetRequiredService<AppDatabase>();
|
||||
|
||||
var now = SystemClock.Instance.GetCurrentInstant();
|
||||
await db.BulkInsertAsync(items.Select(x =>
|
||||
{
|
||||
x.CreatedAt = SystemClock.Instance.GetCurrentInstant();
|
||||
x.CreatedAt = now;
|
||||
x.UpdatedAt = x.CreatedAt;
|
||||
return x;
|
||||
}), config => config.ConflictOption = ConflictOption.Ignore);
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using DysonNetwork.Pass.Wallet;
|
||||
using DysonNetwork.Shared.Cache;
|
||||
using DysonNetwork.Shared.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
@@ -24,7 +23,7 @@ public class ExperienceService(AppDatabase db, SubscriptionService subscriptions
|
||||
{
|
||||
SubscriptionType.Stellar => 1.5,
|
||||
SubscriptionType.Nova => 2,
|
||||
SubscriptionType.Supernova => 2,
|
||||
SubscriptionType.Supernova => 2.5,
|
||||
_ => 1
|
||||
};
|
||||
if (record.Delta >= 0)
|
||||
|
||||
@@ -2,6 +2,7 @@ using System.ComponentModel.DataAnnotations;
|
||||
using DysonNetwork.Shared.Models;
|
||||
using DysonNetwork.Pass.Wallet;
|
||||
using DysonNetwork.Pass.Permission;
|
||||
using DysonNetwork.Shared.Auth;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
@@ -81,7 +82,7 @@ public class LotteryController(AppDatabase db, LotteryService lotteryService) :
|
||||
|
||||
[HttpPost("draw")]
|
||||
[Authorize]
|
||||
[RequiredPermission("maintenance", "lotteries.draw.perform")]
|
||||
[AskPermission("lotteries.draw.perform")]
|
||||
public async Task<IActionResult> PerformLotteryDraw()
|
||||
{
|
||||
await lotteryService.DrawLotteries();
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace DysonNetwork.Pass.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class ReinitalMigration : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "background_id",
|
||||
table: "account_profiles");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "picture_id",
|
||||
table: "account_profiles");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "background_id",
|
||||
table: "account_profiles",
|
||||
type: "character varying(32)",
|
||||
maxLength: 32,
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "picture_id",
|
||||
table: "account_profiles",
|
||||
type: "character varying(32)",
|
||||
maxLength: 32,
|
||||
nullable: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using NodaTime;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace DysonNetwork.Pass.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class RemoveNotification : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "notification_push_subscriptions");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "notifications");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "notification_push_subscriptions",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
account_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
||||
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),
|
||||
last_used_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
||||
provider = table.Column<int>(type: "integer", nullable: false),
|
||||
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false)
|
||||
},
|
||||
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),
|
||||
account_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
content = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: true),
|
||||
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
||||
meta = table.Column<Dictionary<string, object>>(type: "jsonb", nullable: true),
|
||||
priority = table.Column<int>(type: "integer", nullable: false),
|
||||
subtitle = table.Column<string>(type: "character varying(2048)", maxLength: 2048, nullable: true),
|
||||
title = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: true),
|
||||
topic = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false),
|
||||
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||
viewed_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_token_device_id_acco",
|
||||
table: "notification_push_subscriptions",
|
||||
columns: new[] { "device_token", "device_id", "account_id" },
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_notifications_account_id",
|
||||
table: "notifications",
|
||||
column: "account_id");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using NodaTime;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace DysonNetwork.Pass.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddCheckInBackdated : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<Instant>(
|
||||
name: "backdated_from",
|
||||
table: "account_check_in_results",
|
||||
type: "timestamp with time zone",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "backdated_from",
|
||||
table: "account_check_in_results");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,130 +0,0 @@
|
||||
using DysonNetwork.Shared.Models;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using NodaTime;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace DysonNetwork.Pass.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class RemoveDevelopers : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "fk_auth_sessions_custom_apps_app_id",
|
||||
table: "auth_sessions");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "custom_app_secrets");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "custom_apps");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "ix_auth_sessions_app_id",
|
||||
table: "auth_sessions");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "punishments",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
reason = table.Column<string>(type: "character varying(8192)", maxLength: 8192, nullable: false),
|
||||
expired_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
||||
type = table.Column<int>(type: "integer", nullable: false),
|
||||
blocked_permissions = table.Column<List<string>>(type: "jsonb", nullable: true),
|
||||
account_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_punishments", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "fk_punishments_accounts_account_id",
|
||||
column: x => x.account_id,
|
||||
principalTable: "accounts",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_punishments_account_id",
|
||||
table: "punishments",
|
||||
column: "account_id");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "punishments");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "custom_apps",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
background = table.Column<SnCloudFileReferenceObject>(type: "jsonb", nullable: true),
|
||||
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
||||
description = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: true),
|
||||
name = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false),
|
||||
picture = table.Column<SnCloudFileReferenceObject>(type: "jsonb", nullable: true),
|
||||
slug = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false),
|
||||
status = table.Column<int>(type: "integer", nullable: false),
|
||||
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||
verification = table.Column<SnVerificationMark>(type: "jsonb", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_custom_apps", x => x.id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "custom_app_secrets",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
app_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
||||
description = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: true),
|
||||
expired_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
||||
is_oidc = table.Column<bool>(type: "boolean", nullable: false),
|
||||
secret = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false),
|
||||
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_custom_app_secrets", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "fk_custom_app_secrets_custom_apps_app_id",
|
||||
column: x => x.app_id,
|
||||
principalTable: "custom_apps",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_auth_sessions_app_id",
|
||||
table: "auth_sessions",
|
||||
column: "app_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_custom_app_secrets_app_id",
|
||||
table: "custom_app_secrets",
|
||||
column: "app_id");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "fk_auth_sessions_custom_apps_app_id",
|
||||
table: "auth_sessions",
|
||||
column: "app_id",
|
||||
principalTable: "custom_apps",
|
||||
principalColumn: "id");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace DysonNetwork.Pass.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddProfileLinks : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<Dictionary<string, string>>(
|
||||
name: "links",
|
||||
table: "account_profiles",
|
||||
type: "jsonb",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "links",
|
||||
table: "account_profiles");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace DysonNetwork.Pass.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddPublicContact : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "is_public",
|
||||
table: "account_contacts",
|
||||
type: "boolean",
|
||||
nullable: false,
|
||||
defaultValue: false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "is_public",
|
||||
table: "account_contacts");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using NodaTime;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace DysonNetwork.Pass.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddAuthorizeDevice : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "device_id",
|
||||
table: "auth_challenges",
|
||||
type: "character varying(1024)",
|
||||
maxLength: 1024,
|
||||
nullable: true,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(256)",
|
||||
oldMaxLength: 256,
|
||||
oldNullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<Guid>(
|
||||
name: "client_id",
|
||||
table: "auth_challenges",
|
||||
type: "uuid",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "auth_clients",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
device_name = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false),
|
||||
device_label = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: true),
|
||||
device_id = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false),
|
||||
account_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_auth_clients", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "fk_auth_clients_accounts_account_id",
|
||||
column: x => x.account_id,
|
||||
principalTable: "accounts",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_auth_challenges_client_id",
|
||||
table: "auth_challenges",
|
||||
column: "client_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_auth_clients_account_id",
|
||||
table: "auth_clients",
|
||||
column: "account_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_auth_clients_device_id",
|
||||
table: "auth_clients",
|
||||
column: "device_id",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "fk_auth_challenges_auth_clients_client_id",
|
||||
table: "auth_challenges",
|
||||
column: "client_id",
|
||||
principalTable: "auth_clients",
|
||||
principalColumn: "id");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "fk_auth_challenges_auth_clients_client_id",
|
||||
table: "auth_challenges");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "auth_clients");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "ix_auth_challenges_client_id",
|
||||
table: "auth_challenges");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "client_id",
|
||||
table: "auth_challenges");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "device_id",
|
||||
table: "auth_challenges",
|
||||
type: "character varying(256)",
|
||||
maxLength: 256,
|
||||
nullable: true,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(1024)",
|
||||
oldMaxLength: 1024,
|
||||
oldNullable: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace DysonNetwork.Pass.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddAuthDevicePlatform : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "platform",
|
||||
table: "auth_challenges");
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "platform",
|
||||
table: "auth_clients",
|
||||
type: "integer",
|
||||
nullable: false,
|
||||
defaultValue: 0);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "platform",
|
||||
table: "auth_clients");
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "platform",
|
||||
table: "auth_challenges",
|
||||
type: "integer",
|
||||
nullable: false,
|
||||
defaultValue: 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace DysonNetwork.Pass.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class RemoveAuthClientIndex : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropIndex(
|
||||
name: "ix_auth_clients_device_id",
|
||||
table: "auth_clients");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_auth_clients_device_id",
|
||||
table: "auth_clients",
|
||||
column: "device_id",
|
||||
unique: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace DysonNetwork.Pass.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class RemoveChallengeOldDeviceId : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "device_id",
|
||||
table: "auth_challenges");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "device_id",
|
||||
table: "auth_challenges",
|
||||
type: "character varying(1024)",
|
||||
maxLength: 1024,
|
||||
nullable: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,113 +0,0 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using NodaTime;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace DysonNetwork.Pass.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddApiKeys : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "fk_auth_sessions_auth_challenges_challenge_id",
|
||||
table: "auth_sessions");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "label",
|
||||
table: "auth_sessions");
|
||||
|
||||
migrationBuilder.AlterColumn<Guid>(
|
||||
name: "challenge_id",
|
||||
table: "auth_sessions",
|
||||
type: "uuid",
|
||||
nullable: true,
|
||||
oldClrType: typeof(Guid),
|
||||
oldType: "uuid");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "api_keys",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
label = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false),
|
||||
account_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
session_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_api_keys", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "fk_api_keys_accounts_account_id",
|
||||
column: x => x.account_id,
|
||||
principalTable: "accounts",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "fk_api_keys_auth_sessions_session_id",
|
||||
column: x => x.session_id,
|
||||
principalTable: "auth_sessions",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_api_keys_account_id",
|
||||
table: "api_keys",
|
||||
column: "account_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_api_keys_session_id",
|
||||
table: "api_keys",
|
||||
column: "session_id");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "fk_auth_sessions_auth_challenges_challenge_id",
|
||||
table: "auth_sessions",
|
||||
column: "challenge_id",
|
||||
principalTable: "auth_challenges",
|
||||
principalColumn: "id");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "fk_auth_sessions_auth_challenges_challenge_id",
|
||||
table: "auth_sessions");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "api_keys");
|
||||
|
||||
migrationBuilder.AlterColumn<Guid>(
|
||||
name: "challenge_id",
|
||||
table: "auth_sessions",
|
||||
type: "uuid",
|
||||
nullable: false,
|
||||
defaultValue: new Guid("00000000-0000-0000-0000-000000000000"),
|
||||
oldClrType: typeof(Guid),
|
||||
oldType: "uuid",
|
||||
oldNullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "label",
|
||||
table: "auth_sessions",
|
||||
type: "character varying(1024)",
|
||||
maxLength: 1024,
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "fk_auth_sessions_auth_challenges_challenge_id",
|
||||
table: "auth_sessions",
|
||||
column: "challenge_id",
|
||||
principalTable: "auth_challenges",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace DysonNetwork.Pass.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddLevelingBonusMultiplier : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<double>(
|
||||
name: "bonus_multiplier",
|
||||
table: "experience_records",
|
||||
type: "double precision",
|
||||
nullable: false,
|
||||
defaultValue: 0.0);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "bonus_multiplier",
|
||||
table: "experience_records");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace DysonNetwork.Pass.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class CacheSocialCreditsInProfile : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<double>(
|
||||
name: "social_credits",
|
||||
table: "account_profiles",
|
||||
type: "double precision",
|
||||
nullable: false,
|
||||
defaultValue: 0.0);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "social_credits",
|
||||
table: "account_profiles");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace DysonNetwork.Pass.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddOrderProductIdentifier : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "product_identifier",
|
||||
table: "payment_orders",
|
||||
type: "character varying(4096)",
|
||||
maxLength: 4096,
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "product_identifier",
|
||||
table: "payment_orders");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace DysonNetwork.Pass.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddAccountRegion : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "region",
|
||||
table: "accounts",
|
||||
type: "character varying(32)",
|
||||
maxLength: 32,
|
||||
nullable: false,
|
||||
defaultValue: "");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "region",
|
||||
table: "accounts");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
using DysonNetwork.Shared.GeoIp;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using NetTopologySuite.Geometries;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace DysonNetwork.Pass.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class RefactorGeoIpPoint : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.Sql("UPDATE auth_challenges SET location = NULL;");
|
||||
migrationBuilder.Sql("UPDATE action_logs SET location = NULL;");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "location",
|
||||
table: "auth_challenges");
|
||||
|
||||
migrationBuilder.AddColumn<GeoPoint>(
|
||||
name: "location",
|
||||
table: "auth_challenges",
|
||||
type: "jsonb",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "location",
|
||||
table: "action_logs");
|
||||
|
||||
migrationBuilder.AddColumn<GeoPoint>(
|
||||
name: "location",
|
||||
table: "action_logs",
|
||||
type: "jsonb",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "location",
|
||||
table: "auth_challenges");
|
||||
|
||||
migrationBuilder.AddColumn<Point>(
|
||||
name: "location",
|
||||
table: "auth_challenges",
|
||||
type: "geometry",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "location",
|
||||
table: "action_logs");
|
||||
|
||||
migrationBuilder.AddColumn<Point>(
|
||||
name: "location",
|
||||
table: "action_logs",
|
||||
type: "geometry",
|
||||
nullable: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace DysonNetwork.Pass.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class RemoveNetTopo : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AlterDatabase()
|
||||
.OldAnnotation("Npgsql:PostgresExtension:postgis", ",,");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AlterDatabase()
|
||||
.Annotation("Npgsql:PostgresExtension:postgis", ",,");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace DysonNetwork.Pass.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddAutomatedStatus : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "app_identifier",
|
||||
table: "account_statuses",
|
||||
type: "character varying(4096)",
|
||||
maxLength: 4096,
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "is_automated",
|
||||
table: "account_statuses",
|
||||
type: "boolean",
|
||||
nullable: false,
|
||||
defaultValue: false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "app_identifier",
|
||||
table: "account_statuses");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "is_automated",
|
||||
table: "account_statuses");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace DysonNetwork.Pass.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddStatusMeta : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<Dictionary<string, object>>(
|
||||
name: "meta",
|
||||
table: "account_statuses",
|
||||
type: "jsonb",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "meta",
|
||||
table: "account_statuses");
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,133 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json;
|
||||
using DysonNetwork.Shared.GeoIp;
|
||||
using DysonNetwork.Shared.Models;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using NodaTime;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace DysonNetwork.Pass.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddSubscriptionGift : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "wallet_gifts",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
gifter_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
recipient_id = table.Column<Guid>(type: "uuid", nullable: true),
|
||||
gift_code = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: false),
|
||||
message = table.Column<string>(type: "character varying(1000)", maxLength: 1000, nullable: true),
|
||||
subscription_identifier = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: false),
|
||||
base_price = table.Column<decimal>(type: "numeric", nullable: false),
|
||||
final_price = table.Column<decimal>(type: "numeric", nullable: false),
|
||||
status = table.Column<int>(type: "integer", nullable: false),
|
||||
redeemed_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
||||
redeemer_id = table.Column<Guid>(type: "uuid", nullable: true),
|
||||
expires_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||
is_open_gift = table.Column<bool>(type: "boolean", nullable: false),
|
||||
payment_method = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: false),
|
||||
payment_details = table.Column<SnPaymentDetails>(type: "jsonb", nullable: false),
|
||||
coupon_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_wallet_gifts", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "fk_wallet_gifts_accounts_gifter_id",
|
||||
column: x => x.gifter_id,
|
||||
principalTable: "accounts",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "fk_wallet_gifts_accounts_recipient_id",
|
||||
column: x => x.recipient_id,
|
||||
principalTable: "accounts",
|
||||
principalColumn: "id");
|
||||
table.ForeignKey(
|
||||
name: "fk_wallet_gifts_accounts_redeemer_id",
|
||||
column: x => x.redeemer_id,
|
||||
principalTable: "accounts",
|
||||
principalColumn: "id");
|
||||
table.ForeignKey(
|
||||
name: "fk_wallet_gifts_wallet_coupons_coupon_id",
|
||||
column: x => x.coupon_id,
|
||||
principalTable: "wallet_coupons",
|
||||
principalColumn: "id");
|
||||
});
|
||||
|
||||
migrationBuilder.AddColumn<Guid>(
|
||||
name: "gift_id",
|
||||
table: "wallet_subscriptions",
|
||||
type: "uuid",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_wallet_gifts_coupon_id",
|
||||
table: "wallet_gifts",
|
||||
column: "coupon_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_wallet_gifts_gift_code",
|
||||
table: "wallet_gifts",
|
||||
column: "gift_code");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_wallet_gifts_gifter_id",
|
||||
table: "wallet_gifts",
|
||||
column: "gifter_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_wallet_gifts_recipient_id",
|
||||
table: "wallet_gifts",
|
||||
column: "recipient_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_wallet_gifts_redeemer_id",
|
||||
table: "wallet_gifts",
|
||||
column: "redeemer_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_wallet_subscriptions_gift_id",
|
||||
table: "wallet_subscriptions",
|
||||
column: "gift_id",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "fk_wallet_subscriptions_wallet_gifts_gift_id",
|
||||
table: "wallet_subscriptions",
|
||||
column: "gift_id",
|
||||
principalTable: "wallet_gifts",
|
||||
principalColumn: "id");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "fk_wallet_subscriptions_wallet_gifts_gift_id",
|
||||
table: "wallet_subscriptions");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "wallet_gifts");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "ix_wallet_subscriptions_gift_id",
|
||||
table: "wallet_subscriptions");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "gift_id",
|
||||
table: "wallet_subscriptions");
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,81 +0,0 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace DysonNetwork.Pass.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class RefactorSubscriptionRelation : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "fk_wallet_subscriptions_wallet_gifts_gift_id",
|
||||
table: "wallet_subscriptions");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "ix_wallet_subscriptions_gift_id",
|
||||
table: "wallet_subscriptions");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "gift_id",
|
||||
table: "wallet_subscriptions");
|
||||
|
||||
migrationBuilder.AddColumn<Guid>(
|
||||
name: "subscription_id",
|
||||
table: "wallet_gifts",
|
||||
type: "uuid",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_wallet_gifts_subscription_id",
|
||||
table: "wallet_gifts",
|
||||
column: "subscription_id",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "fk_wallet_gifts_wallet_subscriptions_subscription_id",
|
||||
table: "wallet_gifts",
|
||||
column: "subscription_id",
|
||||
principalTable: "wallet_subscriptions",
|
||||
principalColumn: "id");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "fk_wallet_gifts_wallet_subscriptions_subscription_id",
|
||||
table: "wallet_gifts");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "ix_wallet_gifts_subscription_id",
|
||||
table: "wallet_gifts");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "subscription_id",
|
||||
table: "wallet_gifts");
|
||||
|
||||
migrationBuilder.AddColumn<Guid>(
|
||||
name: "gift_id",
|
||||
table: "wallet_subscriptions",
|
||||
type: "uuid",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_wallet_subscriptions_gift_id",
|
||||
table: "wallet_subscriptions",
|
||||
column: "gift_id",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "fk_wallet_subscriptions_wallet_gifts_gift_id",
|
||||
table: "wallet_subscriptions",
|
||||
column: "gift_id",
|
||||
principalTable: "wallet_gifts",
|
||||
principalColumn: "id");
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,99 +0,0 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using NodaTime;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace DysonNetwork.Pass.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddWalletFund : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "wallet_funds",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
currency = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: false),
|
||||
total_amount = table.Column<decimal>(type: "numeric", nullable: false),
|
||||
split_type = table.Column<int>(type: "integer", nullable: false),
|
||||
status = table.Column<int>(type: "integer", nullable: false),
|
||||
message = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: true),
|
||||
creator_account_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
expired_at = table.Column<Instant>(type: "timestamp with time zone", 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_wallet_funds", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "fk_wallet_funds_accounts_creator_account_id",
|
||||
column: x => x.creator_account_id,
|
||||
principalTable: "accounts",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "wallet_fund_recipients",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
fund_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
recipient_account_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
amount = table.Column<decimal>(type: "numeric", nullable: false),
|
||||
is_received = table.Column<bool>(type: "boolean", nullable: false),
|
||||
received_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_wallet_fund_recipients", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "fk_wallet_fund_recipients_accounts_recipient_account_id",
|
||||
column: x => x.recipient_account_id,
|
||||
principalTable: "accounts",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "fk_wallet_fund_recipients_wallet_funds_fund_id",
|
||||
column: x => x.fund_id,
|
||||
principalTable: "wallet_funds",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_wallet_fund_recipients_fund_id",
|
||||
table: "wallet_fund_recipients",
|
||||
column: "fund_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_wallet_fund_recipients_recipient_account_id",
|
||||
table: "wallet_fund_recipients",
|
||||
column: "recipient_account_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_wallet_funds_creator_account_id",
|
||||
table: "wallet_funds",
|
||||
column: "creator_account_id");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "wallet_fund_recipients");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "wallet_funds");
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,160 +0,0 @@
|
||||
using System;
|
||||
using DysonNetwork.Shared.Models;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using NodaTime;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace DysonNetwork.Pass.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddRealmFromSphere : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "realms",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
slug = table.Column<string>(type: "character varying(1024)", maxLength: 1024, 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: false),
|
||||
is_community = table.Column<bool>(type: "boolean", nullable: false),
|
||||
is_public = table.Column<bool>(type: "boolean", nullable: false),
|
||||
picture_id = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: true),
|
||||
background_id = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: true),
|
||||
picture = table.Column<SnCloudFileReferenceObject>(type: "jsonb", nullable: true),
|
||||
background = table.Column<SnCloudFileReferenceObject>(type: "jsonb", nullable: true),
|
||||
verification = table.Column<SnVerificationMark>(type: "jsonb", nullable: true),
|
||||
account_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_realms", x => x.id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "realm_members",
|
||||
columns: table => new
|
||||
{
|
||||
realm_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
account_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
role = table.Column<int>(type: "integer", nullable: false),
|
||||
joined_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
||||
leave_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_realm_members", x => new { x.realm_id, x.account_id });
|
||||
table.ForeignKey(
|
||||
name: "fk_realm_members_realms_realm_id",
|
||||
column: x => x.realm_id,
|
||||
principalTable: "realms",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "sn_chat_room",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
name = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: true),
|
||||
description = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: true),
|
||||
type = table.Column<int>(type: "integer", nullable: false),
|
||||
is_community = table.Column<bool>(type: "boolean", nullable: false),
|
||||
is_public = table.Column<bool>(type: "boolean", nullable: false),
|
||||
picture_id = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: true),
|
||||
background_id = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: true),
|
||||
picture = table.Column<SnCloudFileReferenceObject>(type: "jsonb", nullable: true),
|
||||
background = table.Column<SnCloudFileReferenceObject>(type: "jsonb", nullable: true),
|
||||
realm_id = table.Column<Guid>(type: "uuid", nullable: true),
|
||||
sn_realm_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_sn_chat_room", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "fk_sn_chat_room_realms_sn_realm_id",
|
||||
column: x => x.sn_realm_id,
|
||||
principalTable: "realms",
|
||||
principalColumn: "id");
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "sn_chat_member",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
chat_room_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
account_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
nick = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: true),
|
||||
role = table.Column<int>(type: "integer", nullable: false),
|
||||
notify = table.Column<int>(type: "integer", nullable: false),
|
||||
last_read_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
||||
joined_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
||||
leave_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
||||
is_bot = table.Column<bool>(type: "boolean", nullable: false),
|
||||
break_until = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
||||
timeout_until = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
||||
timeout_cause = table.Column<ChatTimeoutCause>(type: "jsonb", nullable: true),
|
||||
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_sn_chat_member", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "fk_sn_chat_member_sn_chat_room_chat_room_id",
|
||||
column: x => x.chat_room_id,
|
||||
principalTable: "sn_chat_room",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_realms_slug",
|
||||
table: "realms",
|
||||
column: "slug",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_sn_chat_member_chat_room_id",
|
||||
table: "sn_chat_member",
|
||||
column: "chat_room_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_sn_chat_room_sn_realm_id",
|
||||
table: "sn_chat_room",
|
||||
column: "sn_realm_id");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "realm_members");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "sn_chat_member");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "sn_chat_room");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "realms");
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,99 +0,0 @@
|
||||
using System;
|
||||
using DysonNetwork.Shared.Models;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using NodaTime;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace DysonNetwork.Pass.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class RemoveChatRoom : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "sn_chat_member");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "sn_chat_room");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "sn_chat_room",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
background = table.Column<SnCloudFileReferenceObject>(type: "jsonb", nullable: true),
|
||||
background_id = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: true),
|
||||
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
||||
description = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: true),
|
||||
is_community = table.Column<bool>(type: "boolean", nullable: false),
|
||||
is_public = table.Column<bool>(type: "boolean", nullable: false),
|
||||
name = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: true),
|
||||
picture = table.Column<SnCloudFileReferenceObject>(type: "jsonb", nullable: true),
|
||||
picture_id = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: true),
|
||||
realm_id = table.Column<Guid>(type: "uuid", nullable: true),
|
||||
sn_realm_id = table.Column<Guid>(type: "uuid", nullable: true),
|
||||
type = table.Column<int>(type: "integer", nullable: false),
|
||||
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_sn_chat_room", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "fk_sn_chat_room_realms_sn_realm_id",
|
||||
column: x => x.sn_realm_id,
|
||||
principalTable: "realms",
|
||||
principalColumn: "id");
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "sn_chat_member",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
chat_room_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
account_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
break_until = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
||||
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
||||
is_bot = table.Column<bool>(type: "boolean", nullable: false),
|
||||
joined_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
||||
last_read_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
||||
leave_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
||||
nick = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: true),
|
||||
notify = table.Column<int>(type: "integer", nullable: false),
|
||||
role = table.Column<int>(type: "integer", nullable: false),
|
||||
timeout_cause = table.Column<ChatTimeoutCause>(type: "jsonb", nullable: true),
|
||||
timeout_until = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
||||
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_sn_chat_member", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "fk_sn_chat_member_sn_chat_room_chat_room_id",
|
||||
column: x => x.chat_room_id,
|
||||
principalTable: "sn_chat_room",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_sn_chat_member_chat_room_id",
|
||||
table: "sn_chat_member",
|
||||
column: "chat_room_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_sn_chat_room_sn_realm_id",
|
||||
table: "sn_chat_room",
|
||||
column: "sn_realm_id");
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,39 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace DysonNetwork.Pass.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddDetailLotteriesStatus : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<List<int>>(
|
||||
name: "matched_region_one_numbers",
|
||||
table: "lotteries",
|
||||
type: "jsonb",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "matched_region_two_number",
|
||||
table: "lotteries",
|
||||
type: "integer",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "matched_region_one_numbers",
|
||||
table: "lotteries");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "matched_region_two_number",
|
||||
table: "lotteries");
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,80 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using NodaTime;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace DysonNetwork.Pass.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddPresenceActivity : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "background_id",
|
||||
table: "realms");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "picture_id",
|
||||
table: "realms");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "presence_activities",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
type = table.Column<int>(type: "integer", nullable: false),
|
||||
manual_id = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: true),
|
||||
title = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: true),
|
||||
subtitle = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: true),
|
||||
caption = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: true),
|
||||
meta = table.Column<Dictionary<string, object>>(type: "jsonb", nullable: true),
|
||||
lease_minutes = table.Column<int>(type: "integer", nullable: false),
|
||||
lease_expires_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||
account_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_presence_activities", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "fk_presence_activities_accounts_account_id",
|
||||
column: x => x.account_id,
|
||||
principalTable: "accounts",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_presence_activities_account_id",
|
||||
table: "presence_activities",
|
||||
column: "account_id");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "presence_activities");
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "background_id",
|
||||
table: "realms",
|
||||
type: "character varying(32)",
|
||||
maxLength: 32,
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "picture_id",
|
||||
table: "realms",
|
||||
type: "character varying(32)",
|
||||
maxLength: 32,
|
||||
nullable: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,62 +0,0 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace DysonNetwork.Pass.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class EnrichPresenceActivity : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "large_image",
|
||||
table: "presence_activities",
|
||||
type: "character varying(4096)",
|
||||
maxLength: 4096,
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "small_image",
|
||||
table: "presence_activities",
|
||||
type: "character varying(4096)",
|
||||
maxLength: 4096,
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "subtitle_url",
|
||||
table: "presence_activities",
|
||||
type: "character varying(4096)",
|
||||
maxLength: 4096,
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "title_url",
|
||||
table: "presence_activities",
|
||||
type: "character varying(4096)",
|
||||
maxLength: 4096,
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "large_image",
|
||||
table: "presence_activities");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "small_image",
|
||||
table: "presence_activities");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "subtitle_url",
|
||||
table: "presence_activities");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "title_url",
|
||||
table: "presence_activities");
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,29 +0,0 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace DysonNetwork.Pass.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddSocialCreditRecordStatus : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "status",
|
||||
table: "social_credit_records",
|
||||
type: "integer",
|
||||
nullable: false,
|
||||
defaultValue: 0);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "status",
|
||||
table: "social_credit_records");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace DysonNetwork.Pass.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class OpenableFunds : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "is_open",
|
||||
table: "wallet_funds",
|
||||
type: "boolean",
|
||||
nullable: false,
|
||||
defaultValue: false);
|
||||
|
||||
migrationBuilder.AddColumn<decimal>(
|
||||
name: "remaining_amount",
|
||||
table: "wallet_funds",
|
||||
type: "numeric",
|
||||
nullable: false,
|
||||
defaultValue: 0m);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "is_open",
|
||||
table: "wallet_funds");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "remaining_amount",
|
||||
table: "wallet_funds");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace DysonNetwork.Pass.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class OpenFundsTotalSplits : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "amount_of_splits",
|
||||
table: "wallet_funds",
|
||||
type: "integer",
|
||||
nullable: false,
|
||||
defaultValue: 0);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "amount_of_splits",
|
||||
table: "wallet_funds");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json;
|
||||
using DysonNetwork.Pass;
|
||||
using DysonNetwork.Shared.GeoIp;
|
||||
using DysonNetwork.Shared.Geometry;
|
||||
using DysonNetwork.Shared.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
@@ -17,8 +17,8 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
namespace DysonNetwork.Pass.Migrations
|
||||
{
|
||||
[DbContext(typeof(AppDatabase))]
|
||||
[Migration("20251116163407_OpenFundsTotalSplits")]
|
||||
partial class OpenFundsTotalSplits
|
||||
[Migration("20251214092550_InitialMigration")]
|
||||
partial class InitialMigration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
@@ -445,7 +445,7 @@ namespace DysonNetwork.Pass.Migrations
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("last_seen_at");
|
||||
|
||||
b.Property<List<ProfileLink>>("Links")
|
||||
b.Property<List<SnProfileLink>>("Links")
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("links");
|
||||
|
||||
@@ -715,6 +715,103 @@ namespace DysonNetwork.Pass.Migrations
|
||||
b.ToTable("action_logs", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Shared.Models.SnAffiliationResult", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("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>("ResourceIdentifier")
|
||||
.IsRequired()
|
||||
.HasMaxLength(8192)
|
||||
.HasColumnType("character varying(8192)")
|
||||
.HasColumnName("resource_identifier");
|
||||
|
||||
b.Property<Guid>("SpellId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("spell_id");
|
||||
|
||||
b.Property<Instant>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("updated_at");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_affiliation_results");
|
||||
|
||||
b.HasIndex("SpellId")
|
||||
.HasDatabaseName("ix_affiliation_results_spell_id");
|
||||
|
||||
b.ToTable("affiliation_results", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Shared.Models.SnAffiliationSpell", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<Guid?>("AccountId")
|
||||
.HasColumnType("uuid")
|
||||
.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_affiliation_spells");
|
||||
|
||||
b.HasIndex("AccountId")
|
||||
.HasDatabaseName("ix_affiliation_spells_account_id");
|
||||
|
||||
b.HasIndex("Spell")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("ix_affiliation_spells_spell");
|
||||
|
||||
b.ToTable("affiliation_spells", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Shared.Models.SnApiKey", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
@@ -781,10 +878,6 @@ namespace DysonNetwork.Pass.Migrations
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("blacklist_factors");
|
||||
|
||||
b.Property<Guid?>("ClientId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("client_id");
|
||||
|
||||
b.Property<Instant>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("created_at");
|
||||
@@ -793,6 +886,17 @@ namespace DysonNetwork.Pass.Migrations
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("deleted_at");
|
||||
|
||||
b.Property<string>("DeviceId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("character varying(512)")
|
||||
.HasColumnName("device_id");
|
||||
|
||||
b.Property<string>("DeviceName")
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("character varying(1024)")
|
||||
.HasColumnName("device_name");
|
||||
|
||||
b.Property<Instant?>("ExpiredAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("expired_at");
|
||||
@@ -815,6 +919,10 @@ namespace DysonNetwork.Pass.Migrations
|
||||
.HasColumnType("character varying(1024)")
|
||||
.HasColumnName("nonce");
|
||||
|
||||
b.Property<int>("Platform")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("platform");
|
||||
|
||||
b.Property<List<string>>("Scopes")
|
||||
.IsRequired()
|
||||
.HasColumnType("jsonb")
|
||||
@@ -828,10 +936,6 @@ namespace DysonNetwork.Pass.Migrations
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("step_total");
|
||||
|
||||
b.Property<int>("Type")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("type");
|
||||
|
||||
b.Property<Instant>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("updated_at");
|
||||
@@ -847,9 +951,6 @@ namespace DysonNetwork.Pass.Migrations
|
||||
b.HasIndex("AccountId")
|
||||
.HasDatabaseName("ix_auth_challenges_account_id");
|
||||
|
||||
b.HasIndex("ClientId")
|
||||
.HasDatabaseName("ix_auth_challenges_client_id");
|
||||
|
||||
b.ToTable("auth_challenges", (string)null);
|
||||
});
|
||||
|
||||
@@ -921,10 +1022,19 @@ namespace DysonNetwork.Pass.Migrations
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("app_id");
|
||||
|
||||
b.Property<List<string>>("Audiences")
|
||||
.IsRequired()
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("audiences");
|
||||
|
||||
b.Property<Guid?>("ChallengeId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("challenge_id");
|
||||
|
||||
b.Property<Guid?>("ClientId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("client_id");
|
||||
|
||||
b.Property<Instant>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("created_at");
|
||||
@@ -937,22 +1047,52 @@ namespace DysonNetwork.Pass.Migrations
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("expired_at");
|
||||
|
||||
b.Property<string>("IpAddress")
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("character varying(128)")
|
||||
.HasColumnName("ip_address");
|
||||
|
||||
b.Property<Instant?>("LastGrantedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("last_granted_at");
|
||||
|
||||
b.Property<GeoPoint>("Location")
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("location");
|
||||
|
||||
b.Property<Guid?>("ParentSessionId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("parent_session_id");
|
||||
|
||||
b.Property<List<string>>("Scopes")
|
||||
.IsRequired()
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("scopes");
|
||||
|
||||
b.Property<int>("Type")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("type");
|
||||
|
||||
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_sessions");
|
||||
|
||||
b.HasIndex("AccountId")
|
||||
.HasDatabaseName("ix_auth_sessions_account_id");
|
||||
|
||||
b.HasIndex("ChallengeId")
|
||||
.HasDatabaseName("ix_auth_sessions_challenge_id");
|
||||
b.HasIndex("ClientId")
|
||||
.HasDatabaseName("ix_auth_sessions_client_id");
|
||||
|
||||
b.HasIndex("ParentSessionId")
|
||||
.HasDatabaseName("ix_auth_sessions_parent_session_id");
|
||||
|
||||
b.ToTable("auth_sessions", (string)null);
|
||||
});
|
||||
@@ -1317,12 +1457,6 @@ namespace DysonNetwork.Pass.Migrations
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("affected_at");
|
||||
|
||||
b.Property<string>("Area")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("character varying(1024)")
|
||||
.HasColumnName("area");
|
||||
|
||||
b.Property<Instant>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("created_at");
|
||||
@@ -1345,6 +1479,10 @@ namespace DysonNetwork.Pass.Migrations
|
||||
.HasColumnType("character varying(1024)")
|
||||
.HasColumnName("key");
|
||||
|
||||
b.Property<int>("Type")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("type");
|
||||
|
||||
b.Property<Instant>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("updated_at");
|
||||
@@ -1360,8 +1498,8 @@ namespace DysonNetwork.Pass.Migrations
|
||||
b.HasIndex("GroupId")
|
||||
.HasDatabaseName("ix_permission_nodes_group_id");
|
||||
|
||||
b.HasIndex("Key", "Area", "Actor")
|
||||
.HasDatabaseName("ix_permission_nodes_key_area_actor");
|
||||
b.HasIndex("Key", "Actor")
|
||||
.HasDatabaseName("ix_permission_nodes_key_actor");
|
||||
|
||||
b.ToTable("permission_nodes", (string)null);
|
||||
});
|
||||
@@ -2347,6 +2485,28 @@ namespace DysonNetwork.Pass.Migrations
|
||||
b.Navigation("Account");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Shared.Models.SnAffiliationResult", b =>
|
||||
{
|
||||
b.HasOne("DysonNetwork.Shared.Models.SnAffiliationSpell", "Spell")
|
||||
.WithMany()
|
||||
.HasForeignKey("SpellId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_affiliation_results_affiliation_spells_spell_id");
|
||||
|
||||
b.Navigation("Spell");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Shared.Models.SnAffiliationSpell", b =>
|
||||
{
|
||||
b.HasOne("DysonNetwork.Shared.Models.SnAccount", "Account")
|
||||
.WithMany()
|
||||
.HasForeignKey("AccountId")
|
||||
.HasConstraintName("fk_affiliation_spells_accounts_account_id");
|
||||
|
||||
b.Navigation("Account");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Shared.Models.SnApiKey", b =>
|
||||
{
|
||||
b.HasOne("DysonNetwork.Shared.Models.SnAccount", "Account")
|
||||
@@ -2377,14 +2537,7 @@ namespace DysonNetwork.Pass.Migrations
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_auth_challenges_accounts_account_id");
|
||||
|
||||
b.HasOne("DysonNetwork.Shared.Models.SnAuthClient", "Client")
|
||||
.WithMany()
|
||||
.HasForeignKey("ClientId")
|
||||
.HasConstraintName("fk_auth_challenges_auth_clients_client_id");
|
||||
|
||||
b.Navigation("Account");
|
||||
|
||||
b.Navigation("Client");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Shared.Models.SnAuthClient", b =>
|
||||
@@ -2408,14 +2561,21 @@ namespace DysonNetwork.Pass.Migrations
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_auth_sessions_accounts_account_id");
|
||||
|
||||
b.HasOne("DysonNetwork.Shared.Models.SnAuthChallenge", "Challenge")
|
||||
b.HasOne("DysonNetwork.Shared.Models.SnAuthClient", "Client")
|
||||
.WithMany()
|
||||
.HasForeignKey("ChallengeId")
|
||||
.HasConstraintName("fk_auth_sessions_auth_challenges_challenge_id");
|
||||
.HasForeignKey("ClientId")
|
||||
.HasConstraintName("fk_auth_sessions_auth_clients_client_id");
|
||||
|
||||
b.HasOne("DysonNetwork.Shared.Models.SnAuthSession", "ParentSession")
|
||||
.WithMany()
|
||||
.HasForeignKey("ParentSessionId")
|
||||
.HasConstraintName("fk_auth_sessions_auth_sessions_parent_session_id");
|
||||
|
||||
b.Navigation("Account");
|
||||
|
||||
b.Navigation("Challenge");
|
||||
b.Navigation("Client");
|
||||
|
||||
b.Navigation("ParentSession");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Shared.Models.SnCheckInResult", b =>
|
||||
@@ -1,7 +1,9 @@
|
||||
using System.Text.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json;
|
||||
using DysonNetwork.Shared.Geometry;
|
||||
using DysonNetwork.Shared.Models;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using NetTopologySuite.Geometries;
|
||||
using NodaTime;
|
||||
|
||||
#nullable disable
|
||||
@@ -14,9 +16,6 @@ namespace DysonNetwork.Pass.Migrations
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AlterDatabase()
|
||||
.Annotation("Npgsql:PostgresExtension:postgis", ",,");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "accounts",
|
||||
columns: table => new
|
||||
@@ -25,8 +24,10 @@ namespace DysonNetwork.Pass.Migrations
|
||||
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),
|
||||
region = 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),
|
||||
automated_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)
|
||||
@@ -37,24 +38,23 @@ namespace DysonNetwork.Pass.Migrations
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "custom_apps",
|
||||
name: "lottery_records",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
slug = table.Column<string>(type: "character varying(1024)", maxLength: 1024, 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),
|
||||
status = table.Column<int>(type: "integer", nullable: false),
|
||||
picture = table.Column<SnCloudFileReferenceObject>(type: "jsonb", nullable: true),
|
||||
background = table.Column<SnCloudFileReferenceObject>(type: "jsonb", nullable: true),
|
||||
verification = table.Column<SnVerificationMark>(type: "jsonb", nullable: true),
|
||||
draw_date = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||
winning_region_one_numbers = table.Column<List<int>>(type: "jsonb", nullable: false),
|
||||
winning_region_two_number = table.Column<int>(type: "integer", nullable: false),
|
||||
total_tickets = table.Column<int>(type: "integer", nullable: false),
|
||||
total_prizes_awarded = table.Column<int>(type: "integer", nullable: false),
|
||||
total_prize_amount = 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_custom_apps", x => x.id);
|
||||
table.PrimaryKey("pk_lottery_records", x => x.id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
@@ -72,6 +72,29 @@ namespace DysonNetwork.Pass.Migrations
|
||||
table.PrimaryKey("pk_permission_groups", x => x.id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "realms",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
slug = table.Column<string>(type: "character varying(1024)", maxLength: 1024, 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: false),
|
||||
is_community = table.Column<bool>(type: "boolean", nullable: false),
|
||||
is_public = table.Column<bool>(type: "boolean", nullable: false),
|
||||
picture = table.Column<SnCloudFileReferenceObject>(type: "jsonb", nullable: true),
|
||||
background = table.Column<SnCloudFileReferenceObject>(type: "jsonb", nullable: true),
|
||||
verification = table.Column<SnVerificationMark>(type: "jsonb", nullable: true),
|
||||
account_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_realms", x => x.id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "wallet_coupons",
|
||||
columns: table => new
|
||||
@@ -156,6 +179,7 @@ namespace DysonNetwork.Pass.Migrations
|
||||
reward_experience = table.Column<int>(type: "integer", nullable: true),
|
||||
tips = table.Column<ICollection<CheckInFortuneTip>>(type: "jsonb", nullable: false),
|
||||
account_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
backdated_from = 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)
|
||||
@@ -206,6 +230,7 @@ namespace DysonNetwork.Pass.Migrations
|
||||
type = table.Column<int>(type: "integer", nullable: false),
|
||||
verified_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
||||
is_primary = table.Column<bool>(type: "boolean", nullable: false),
|
||||
is_public = table.Column<bool>(type: "boolean", nullable: false),
|
||||
content = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false),
|
||||
account_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||
@@ -236,13 +261,14 @@ namespace DysonNetwork.Pass.Migrations
|
||||
pronouns = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: true),
|
||||
time_zone = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: true),
|
||||
location = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: true),
|
||||
links = table.Column<List<SnProfileLink>>(type: "jsonb", nullable: true),
|
||||
username_color = table.Column<UsernameColor>(type: "jsonb", nullable: true),
|
||||
birthday = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
||||
last_seen_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
||||
verification = table.Column<SnVerificationMark>(type: "jsonb", nullable: true),
|
||||
active_badge = table.Column<SnAccountBadge>(type: "jsonb", nullable: true),
|
||||
active_badge = table.Column<SnAccountBadgeRef>(type: "jsonb", nullable: true),
|
||||
experience = table.Column<int>(type: "integer", nullable: false),
|
||||
picture_id = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: true),
|
||||
background_id = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: true),
|
||||
social_credits = table.Column<double>(type: "double precision", nullable: false),
|
||||
picture = table.Column<SnCloudFileReferenceObject>(type: "jsonb", nullable: true),
|
||||
background = table.Column<SnCloudFileReferenceObject>(type: "jsonb", nullable: true),
|
||||
account_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
@@ -299,7 +325,10 @@ namespace DysonNetwork.Pass.Migrations
|
||||
is_invisible = table.Column<bool>(type: "boolean", nullable: false),
|
||||
is_not_disturb = table.Column<bool>(type: "boolean", nullable: false),
|
||||
label = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: true),
|
||||
meta = table.Column<Dictionary<string, object>>(type: "jsonb", nullable: true),
|
||||
cleared_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
||||
app_identifier = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: true),
|
||||
is_automated = table.Column<bool>(type: "boolean", nullable: false),
|
||||
account_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||
@@ -325,7 +354,7 @@ namespace DysonNetwork.Pass.Migrations
|
||||
meta = table.Column<Dictionary<string, object>>(type: "jsonb", nullable: false),
|
||||
user_agent = table.Column<string>(type: "character varying(512)", maxLength: 512, nullable: true),
|
||||
ip_address = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: true),
|
||||
location = table.Column<Point>(type: "geometry", nullable: true),
|
||||
location = table.Column<GeoPoint>(type: "jsonb", nullable: true),
|
||||
account_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
session_id = table.Column<Guid>(type: "uuid", nullable: true),
|
||||
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||
@@ -343,6 +372,31 @@ namespace DysonNetwork.Pass.Migrations
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "affiliation_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<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_affiliation_spells", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "fk_affiliation_spells_accounts_account_id",
|
||||
column: x => x.account_id,
|
||||
principalTable: "accounts",
|
||||
principalColumn: "id");
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "auth_challenges",
|
||||
columns: table => new
|
||||
@@ -352,16 +406,16 @@ namespace DysonNetwork.Pass.Migrations
|
||||
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),
|
||||
platform = table.Column<int>(type: "integer", nullable: false),
|
||||
type = table.Column<int>(type: "integer", nullable: false),
|
||||
blacklist_factors = table.Column<List<Guid>>(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),
|
||||
device_id = table.Column<string>(type: "character varying(512)", maxLength: 512, nullable: false),
|
||||
device_name = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: true),
|
||||
platform = table.Column<int>(type: "integer", nullable: false),
|
||||
nonce = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: true),
|
||||
location = table.Column<Point>(type: "geometry", nullable: true),
|
||||
location = table.Column<GeoPoint>(type: "jsonb", nullable: true),
|
||||
account_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||
@@ -378,6 +432,31 @@ namespace DysonNetwork.Pass.Migrations
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "auth_clients",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
platform = table.Column<int>(type: "integer", nullable: false),
|
||||
device_name = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false),
|
||||
device_label = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: true),
|
||||
device_id = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false),
|
||||
account_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_auth_clients", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "fk_auth_clients_accounts_account_id",
|
||||
column: x => x.account_id,
|
||||
principalTable: "accounts",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "badges",
|
||||
columns: table => new
|
||||
@@ -405,6 +484,59 @@ namespace DysonNetwork.Pass.Migrations
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "experience_records",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
reason_type = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false),
|
||||
reason = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false),
|
||||
delta = table.Column<long>(type: "bigint", nullable: false),
|
||||
bonus_multiplier = table.Column<double>(type: "double precision", nullable: false),
|
||||
account_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_experience_records", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "fk_experience_records_accounts_account_id",
|
||||
column: x => x.account_id,
|
||||
principalTable: "accounts",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "lotteries",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
account_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
region_one_numbers = table.Column<List<int>>(type: "jsonb", nullable: false),
|
||||
region_two_number = table.Column<int>(type: "integer", nullable: false),
|
||||
multiplier = table.Column<int>(type: "integer", nullable: false),
|
||||
draw_status = table.Column<int>(type: "integer", nullable: false),
|
||||
draw_date = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
||||
matched_region_one_numbers = table.Column<List<int>>(type: "jsonb", nullable: true),
|
||||
matched_region_two_number = table.Column<int>(type: "integer", 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_lotteries", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "fk_lotteries_accounts_account_id",
|
||||
column: x => x.account_id,
|
||||
principalTable: "accounts",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "magic_spells",
|
||||
columns: table => new
|
||||
@@ -431,14 +563,22 @@ namespace DysonNetwork.Pass.Migrations
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "notification_push_subscriptions",
|
||||
name: "presence_activities",
|
||||
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),
|
||||
type = table.Column<int>(type: "integer", nullable: false),
|
||||
manual_id = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: true),
|
||||
title = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: true),
|
||||
subtitle = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: true),
|
||||
caption = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: true),
|
||||
large_image = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: true),
|
||||
small_image = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: true),
|
||||
title_url = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: true),
|
||||
subtitle_url = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: true),
|
||||
meta = table.Column<Dictionary<string, object>>(type: "jsonb", nullable: true),
|
||||
lease_minutes = table.Column<int>(type: "integer", nullable: false),
|
||||
lease_expires_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||
account_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||
@@ -446,9 +586,9 @@ namespace DysonNetwork.Pass.Migrations
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_notification_push_subscriptions", x => x.id);
|
||||
table.PrimaryKey("pk_presence_activities", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "fk_notification_push_subscriptions_accounts_account_id",
|
||||
name: "fk_presence_activities_accounts_account_id",
|
||||
column: x => x.account_id,
|
||||
principalTable: "accounts",
|
||||
principalColumn: "id",
|
||||
@@ -456,17 +596,14 @@ namespace DysonNetwork.Pass.Migrations
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "notifications",
|
||||
name: "punishments",
|
||||
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),
|
||||
reason = table.Column<string>(type: "character varying(8192)", maxLength: 8192, nullable: false),
|
||||
expired_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
||||
type = table.Column<int>(type: "integer", nullable: false),
|
||||
blocked_permissions = table.Column<List<string>>(type: "jsonb", nullable: true),
|
||||
account_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||
@@ -474,15 +611,71 @@ namespace DysonNetwork.Pass.Migrations
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_notifications", x => x.id);
|
||||
table.PrimaryKey("pk_punishments", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "fk_notifications_accounts_account_id",
|
||||
name: "fk_punishments_accounts_account_id",
|
||||
column: x => x.account_id,
|
||||
principalTable: "accounts",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "social_credit_records",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
reason_type = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false),
|
||||
reason = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false),
|
||||
delta = table.Column<double>(type: "double precision", nullable: false),
|
||||
status = table.Column<int>(type: "integer", nullable: false),
|
||||
expired_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
||||
account_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_social_credit_records", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "fk_social_credit_records_accounts_account_id",
|
||||
column: x => x.account_id,
|
||||
principalTable: "accounts",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "wallet_funds",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
currency = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: false),
|
||||
total_amount = table.Column<decimal>(type: "numeric", nullable: false),
|
||||
remaining_amount = table.Column<decimal>(type: "numeric", nullable: false),
|
||||
amount_of_splits = table.Column<int>(type: "integer", nullable: false),
|
||||
split_type = table.Column<int>(type: "integer", nullable: false),
|
||||
status = table.Column<int>(type: "integer", nullable: false),
|
||||
message = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: true),
|
||||
is_open = table.Column<bool>(type: "boolean", nullable: false),
|
||||
creator_account_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
expired_at = table.Column<Instant>(type: "timestamp with time zone", 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_wallet_funds", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "fk_wallet_funds_accounts_creator_account_id",
|
||||
column: x => x.creator_account_id,
|
||||
principalTable: "accounts",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "wallets",
|
||||
columns: table => new
|
||||
@@ -504,31 +697,6 @@ namespace DysonNetwork.Pass.Migrations
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "custom_app_secrets",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
secret = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false),
|
||||
description = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: true),
|
||||
expired_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
||||
is_oidc = table.Column<bool>(type: "boolean", nullable: false),
|
||||
app_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_custom_app_secrets", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "fk_custom_app_secrets_custom_apps_app_id",
|
||||
column: x => x.app_id,
|
||||
principalTable: "custom_apps",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "permission_group_members",
|
||||
columns: table => new
|
||||
@@ -557,8 +725,8 @@ namespace DysonNetwork.Pass.Migrations
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
type = table.Column<int>(type: "integer", 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),
|
||||
@@ -578,6 +746,30 @@ namespace DysonNetwork.Pass.Migrations
|
||||
principalColumn: "id");
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "realm_members",
|
||||
columns: table => new
|
||||
{
|
||||
realm_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
account_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
role = table.Column<int>(type: "integer", nullable: false),
|
||||
joined_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
||||
leave_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_realm_members", x => new { x.realm_id, x.account_id });
|
||||
table.ForeignKey(
|
||||
name: "fk_realm_members_realms_realm_id",
|
||||
column: x => x.realm_id,
|
||||
principalTable: "realms",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "wallet_subscriptions",
|
||||
columns: table => new
|
||||
@@ -615,16 +807,45 @@ namespace DysonNetwork.Pass.Migrations
|
||||
principalColumn: "id");
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "affiliation_results",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
resource_identifier = table.Column<string>(type: "character varying(8192)", maxLength: 8192, nullable: false),
|
||||
spell_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_affiliation_results", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "fk_affiliation_results_affiliation_spells_spell_id",
|
||||
column: x => x.spell_id,
|
||||
principalTable: "affiliation_spells",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "auth_sessions",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
label = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: true),
|
||||
type = table.Column<int>(type: "integer", 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),
|
||||
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),
|
||||
location = table.Column<GeoPoint>(type: "jsonb", nullable: true),
|
||||
account_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
challenge_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
client_id = table.Column<Guid>(type: "uuid", nullable: true),
|
||||
parent_session_id = table.Column<Guid>(type: "uuid", nullable: true),
|
||||
challenge_id = table.Column<Guid>(type: "uuid", nullable: true),
|
||||
app_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),
|
||||
@@ -640,16 +861,46 @@ namespace DysonNetwork.Pass.Migrations
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "fk_auth_sessions_auth_challenges_challenge_id",
|
||||
column: x => x.challenge_id,
|
||||
principalTable: "auth_challenges",
|
||||
name: "fk_auth_sessions_auth_clients_client_id",
|
||||
column: x => x.client_id,
|
||||
principalTable: "auth_clients",
|
||||
principalColumn: "id");
|
||||
table.ForeignKey(
|
||||
name: "fk_auth_sessions_auth_sessions_parent_session_id",
|
||||
column: x => x.parent_session_id,
|
||||
principalTable: "auth_sessions",
|
||||
principalColumn: "id");
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "wallet_fund_recipients",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
fund_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
recipient_account_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
amount = table.Column<decimal>(type: "numeric", nullable: false),
|
||||
is_received = table.Column<bool>(type: "boolean", nullable: false),
|
||||
received_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_wallet_fund_recipients", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "fk_wallet_fund_recipients_accounts_recipient_account_id",
|
||||
column: x => x.recipient_account_id,
|
||||
principalTable: "accounts",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "fk_auth_sessions_custom_apps_app_id",
|
||||
column: x => x.app_id,
|
||||
principalTable: "custom_apps",
|
||||
principalColumn: "id");
|
||||
name: "fk_wallet_fund_recipients_wallet_funds_fund_id",
|
||||
column: x => x.fund_id,
|
||||
principalTable: "wallet_funds",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
@@ -705,6 +956,91 @@ namespace DysonNetwork.Pass.Migrations
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "wallet_gifts",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
gifter_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
recipient_id = table.Column<Guid>(type: "uuid", nullable: true),
|
||||
gift_code = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: false),
|
||||
message = table.Column<string>(type: "character varying(1000)", maxLength: 1000, nullable: true),
|
||||
subscription_identifier = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: false),
|
||||
base_price = table.Column<decimal>(type: "numeric", nullable: false),
|
||||
final_price = table.Column<decimal>(type: "numeric", nullable: false),
|
||||
status = table.Column<int>(type: "integer", nullable: false),
|
||||
redeemed_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
||||
redeemer_id = table.Column<Guid>(type: "uuid", nullable: true),
|
||||
subscription_id = table.Column<Guid>(type: "uuid", nullable: true),
|
||||
expires_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||
is_open_gift = table.Column<bool>(type: "boolean", nullable: false),
|
||||
payment_method = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: false),
|
||||
payment_details = table.Column<SnPaymentDetails>(type: "jsonb", nullable: false),
|
||||
coupon_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_wallet_gifts", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "fk_wallet_gifts_accounts_gifter_id",
|
||||
column: x => x.gifter_id,
|
||||
principalTable: "accounts",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "fk_wallet_gifts_accounts_recipient_id",
|
||||
column: x => x.recipient_id,
|
||||
principalTable: "accounts",
|
||||
principalColumn: "id");
|
||||
table.ForeignKey(
|
||||
name: "fk_wallet_gifts_accounts_redeemer_id",
|
||||
column: x => x.redeemer_id,
|
||||
principalTable: "accounts",
|
||||
principalColumn: "id");
|
||||
table.ForeignKey(
|
||||
name: "fk_wallet_gifts_wallet_coupons_coupon_id",
|
||||
column: x => x.coupon_id,
|
||||
principalTable: "wallet_coupons",
|
||||
principalColumn: "id");
|
||||
table.ForeignKey(
|
||||
name: "fk_wallet_gifts_wallet_subscriptions_subscription_id",
|
||||
column: x => x.subscription_id,
|
||||
principalTable: "wallet_subscriptions",
|
||||
principalColumn: "id");
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "api_keys",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
label = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false),
|
||||
account_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
session_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_api_keys", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "fk_api_keys_accounts_account_id",
|
||||
column: x => x.account_id,
|
||||
principalTable: "accounts",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "fk_api_keys_auth_sessions_session_id",
|
||||
column: x => x.session_id,
|
||||
principalTable: "auth_sessions",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "payment_orders",
|
||||
columns: table => new
|
||||
@@ -714,6 +1050,7 @@ namespace DysonNetwork.Pass.Migrations
|
||||
currency = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: false),
|
||||
remarks = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: true),
|
||||
app_identifier = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: true),
|
||||
product_identifier = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: true),
|
||||
meta = table.Column<Dictionary<string, object>>(type: "jsonb", nullable: true),
|
||||
amount = table.Column<decimal>(type: "numeric", nullable: false),
|
||||
expired_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||
@@ -790,25 +1127,56 @@ namespace DysonNetwork.Pass.Migrations
|
||||
table: "action_logs",
|
||||
column: "account_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_affiliation_results_spell_id",
|
||||
table: "affiliation_results",
|
||||
column: "spell_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_affiliation_spells_account_id",
|
||||
table: "affiliation_spells",
|
||||
column: "account_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_affiliation_spells_spell",
|
||||
table: "affiliation_spells",
|
||||
column: "spell",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_api_keys_account_id",
|
||||
table: "api_keys",
|
||||
column: "account_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_api_keys_session_id",
|
||||
table: "api_keys",
|
||||
column: "session_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_auth_challenges_account_id",
|
||||
table: "auth_challenges",
|
||||
column: "account_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_auth_clients_account_id",
|
||||
table: "auth_clients",
|
||||
column: "account_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_auth_sessions_account_id",
|
||||
table: "auth_sessions",
|
||||
column: "account_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_auth_sessions_app_id",
|
||||
name: "ix_auth_sessions_client_id",
|
||||
table: "auth_sessions",
|
||||
column: "app_id");
|
||||
column: "client_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_auth_sessions_challenge_id",
|
||||
name: "ix_auth_sessions_parent_session_id",
|
||||
table: "auth_sessions",
|
||||
column: "challenge_id");
|
||||
column: "parent_session_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_badges_account_id",
|
||||
@@ -816,9 +1184,14 @@ namespace DysonNetwork.Pass.Migrations
|
||||
column: "account_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_custom_app_secrets_app_id",
|
||||
table: "custom_app_secrets",
|
||||
column: "app_id");
|
||||
name: "ix_experience_records_account_id",
|
||||
table: "experience_records",
|
||||
column: "account_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_lotteries_account_id",
|
||||
table: "lotteries",
|
||||
column: "account_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_magic_spells_account_id",
|
||||
@@ -831,22 +1204,6 @@ namespace DysonNetwork.Pass.Migrations
|
||||
column: "spell",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_notification_push_subscriptions_account_id",
|
||||
table: "notification_push_subscriptions",
|
||||
column: "account_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_notification_push_subscriptions_device_token_device_id_acco",
|
||||
table: "notification_push_subscriptions",
|
||||
columns: new[] { "device_token", "device_id", "account_id" },
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_notifications_account_id",
|
||||
table: "notifications",
|
||||
column: "account_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_payment_orders_payee_wallet_id",
|
||||
table: "payment_orders",
|
||||
@@ -873,9 +1230,76 @@ namespace DysonNetwork.Pass.Migrations
|
||||
column: "group_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_permission_nodes_key_area_actor",
|
||||
name: "ix_permission_nodes_key_actor",
|
||||
table: "permission_nodes",
|
||||
columns: new[] { "key", "area", "actor" });
|
||||
columns: new[] { "key", "actor" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_presence_activities_account_id",
|
||||
table: "presence_activities",
|
||||
column: "account_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_punishments_account_id",
|
||||
table: "punishments",
|
||||
column: "account_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_realms_slug",
|
||||
table: "realms",
|
||||
column: "slug",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_social_credit_records_account_id",
|
||||
table: "social_credit_records",
|
||||
column: "account_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_wallet_fund_recipients_fund_id",
|
||||
table: "wallet_fund_recipients",
|
||||
column: "fund_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_wallet_fund_recipients_recipient_account_id",
|
||||
table: "wallet_fund_recipients",
|
||||
column: "recipient_account_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_wallet_funds_creator_account_id",
|
||||
table: "wallet_funds",
|
||||
column: "creator_account_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_wallet_gifts_coupon_id",
|
||||
table: "wallet_gifts",
|
||||
column: "coupon_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_wallet_gifts_gift_code",
|
||||
table: "wallet_gifts",
|
||||
column: "gift_code");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_wallet_gifts_gifter_id",
|
||||
table: "wallet_gifts",
|
||||
column: "gifter_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_wallet_gifts_recipient_id",
|
||||
table: "wallet_gifts",
|
||||
column: "recipient_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_wallet_gifts_redeemer_id",
|
||||
table: "wallet_gifts",
|
||||
column: "redeemer_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_wallet_gifts_subscription_id",
|
||||
table: "wallet_gifts",
|
||||
column: "subscription_id",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_wallet_pockets_wallet_id",
|
||||
@@ -887,6 +1311,16 @@ namespace DysonNetwork.Pass.Migrations
|
||||
table: "wallet_subscriptions",
|
||||
column: "account_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_wallet_subscriptions_account_id_identifier",
|
||||
table: "wallet_subscriptions",
|
||||
columns: new[] { "account_id", "identifier" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_wallet_subscriptions_account_id_is_active",
|
||||
table: "wallet_subscriptions",
|
||||
columns: new[] { "account_id", "is_active" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_wallet_subscriptions_coupon_id",
|
||||
table: "wallet_subscriptions",
|
||||
@@ -897,6 +1331,11 @@ namespace DysonNetwork.Pass.Migrations
|
||||
table: "wallet_subscriptions",
|
||||
column: "identifier");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_wallet_subscriptions_status",
|
||||
table: "wallet_subscriptions",
|
||||
column: "status");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_wallets_account_id",
|
||||
table: "wallets",
|
||||
@@ -934,23 +1373,29 @@ namespace DysonNetwork.Pass.Migrations
|
||||
name: "action_logs");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "auth_sessions");
|
||||
name: "affiliation_results");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "api_keys");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "auth_challenges");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "badges");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "custom_app_secrets");
|
||||
name: "experience_records");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "lotteries");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "lottery_records");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "magic_spells");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "notification_push_subscriptions");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "notifications");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "payment_orders");
|
||||
|
||||
@@ -960,17 +1405,32 @@ namespace DysonNetwork.Pass.Migrations
|
||||
migrationBuilder.DropTable(
|
||||
name: "permission_nodes");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "presence_activities");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "punishments");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "realm_members");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "social_credit_records");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "wallet_fund_recipients");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "wallet_gifts");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "wallet_pockets");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "wallet_subscriptions");
|
||||
name: "affiliation_spells");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "auth_challenges");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "custom_apps");
|
||||
name: "auth_sessions");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "payment_transactions");
|
||||
@@ -979,11 +1439,23 @@ namespace DysonNetwork.Pass.Migrations
|
||||
name: "permission_groups");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "wallet_coupons");
|
||||
name: "realms");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "wallet_funds");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "wallet_subscriptions");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "auth_clients");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "wallets");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "wallet_coupons");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "accounts");
|
||||
}
|
||||
@@ -3,7 +3,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json;
|
||||
using DysonNetwork.Pass;
|
||||
using DysonNetwork.Shared.GeoIp;
|
||||
using DysonNetwork.Shared.Geometry;
|
||||
using DysonNetwork.Shared.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
@@ -17,8 +17,8 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
namespace DysonNetwork.Pass.Migrations
|
||||
{
|
||||
[DbContext(typeof(AppDatabase))]
|
||||
[Migration("20251116153151_OpenableFunds")]
|
||||
partial class OpenableFunds
|
||||
[Migration("20251225145146_AddRewindPoint")]
|
||||
partial class AddRewindPoint
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
@@ -445,7 +445,7 @@ namespace DysonNetwork.Pass.Migrations
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("last_seen_at");
|
||||
|
||||
b.Property<List<ProfileLink>>("Links")
|
||||
b.Property<List<SnProfileLink>>("Links")
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("links");
|
||||
|
||||
@@ -715,6 +715,103 @@ namespace DysonNetwork.Pass.Migrations
|
||||
b.ToTable("action_logs", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Shared.Models.SnAffiliationResult", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("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>("ResourceIdentifier")
|
||||
.IsRequired()
|
||||
.HasMaxLength(8192)
|
||||
.HasColumnType("character varying(8192)")
|
||||
.HasColumnName("resource_identifier");
|
||||
|
||||
b.Property<Guid>("SpellId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("spell_id");
|
||||
|
||||
b.Property<Instant>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("updated_at");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_affiliation_results");
|
||||
|
||||
b.HasIndex("SpellId")
|
||||
.HasDatabaseName("ix_affiliation_results_spell_id");
|
||||
|
||||
b.ToTable("affiliation_results", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Shared.Models.SnAffiliationSpell", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<Guid?>("AccountId")
|
||||
.HasColumnType("uuid")
|
||||
.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_affiliation_spells");
|
||||
|
||||
b.HasIndex("AccountId")
|
||||
.HasDatabaseName("ix_affiliation_spells_account_id");
|
||||
|
||||
b.HasIndex("Spell")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("ix_affiliation_spells_spell");
|
||||
|
||||
b.ToTable("affiliation_spells", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Shared.Models.SnApiKey", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
@@ -781,10 +878,6 @@ namespace DysonNetwork.Pass.Migrations
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("blacklist_factors");
|
||||
|
||||
b.Property<Guid?>("ClientId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("client_id");
|
||||
|
||||
b.Property<Instant>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("created_at");
|
||||
@@ -793,6 +886,17 @@ namespace DysonNetwork.Pass.Migrations
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("deleted_at");
|
||||
|
||||
b.Property<string>("DeviceId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("character varying(512)")
|
||||
.HasColumnName("device_id");
|
||||
|
||||
b.Property<string>("DeviceName")
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("character varying(1024)")
|
||||
.HasColumnName("device_name");
|
||||
|
||||
b.Property<Instant?>("ExpiredAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("expired_at");
|
||||
@@ -815,6 +919,10 @@ namespace DysonNetwork.Pass.Migrations
|
||||
.HasColumnType("character varying(1024)")
|
||||
.HasColumnName("nonce");
|
||||
|
||||
b.Property<int>("Platform")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("platform");
|
||||
|
||||
b.Property<List<string>>("Scopes")
|
||||
.IsRequired()
|
||||
.HasColumnType("jsonb")
|
||||
@@ -828,10 +936,6 @@ namespace DysonNetwork.Pass.Migrations
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("step_total");
|
||||
|
||||
b.Property<int>("Type")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("type");
|
||||
|
||||
b.Property<Instant>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("updated_at");
|
||||
@@ -847,9 +951,6 @@ namespace DysonNetwork.Pass.Migrations
|
||||
b.HasIndex("AccountId")
|
||||
.HasDatabaseName("ix_auth_challenges_account_id");
|
||||
|
||||
b.HasIndex("ClientId")
|
||||
.HasDatabaseName("ix_auth_challenges_client_id");
|
||||
|
||||
b.ToTable("auth_challenges", (string)null);
|
||||
});
|
||||
|
||||
@@ -921,10 +1022,19 @@ namespace DysonNetwork.Pass.Migrations
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("app_id");
|
||||
|
||||
b.Property<List<string>>("Audiences")
|
||||
.IsRequired()
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("audiences");
|
||||
|
||||
b.Property<Guid?>("ChallengeId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("challenge_id");
|
||||
|
||||
b.Property<Guid?>("ClientId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("client_id");
|
||||
|
||||
b.Property<Instant>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("created_at");
|
||||
@@ -937,22 +1047,52 @@ namespace DysonNetwork.Pass.Migrations
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("expired_at");
|
||||
|
||||
b.Property<string>("IpAddress")
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("character varying(128)")
|
||||
.HasColumnName("ip_address");
|
||||
|
||||
b.Property<Instant?>("LastGrantedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("last_granted_at");
|
||||
|
||||
b.Property<GeoPoint>("Location")
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("location");
|
||||
|
||||
b.Property<Guid?>("ParentSessionId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("parent_session_id");
|
||||
|
||||
b.Property<List<string>>("Scopes")
|
||||
.IsRequired()
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("scopes");
|
||||
|
||||
b.Property<int>("Type")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("type");
|
||||
|
||||
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_sessions");
|
||||
|
||||
b.HasIndex("AccountId")
|
||||
.HasDatabaseName("ix_auth_sessions_account_id");
|
||||
|
||||
b.HasIndex("ChallengeId")
|
||||
.HasDatabaseName("ix_auth_sessions_challenge_id");
|
||||
b.HasIndex("ClientId")
|
||||
.HasDatabaseName("ix_auth_sessions_client_id");
|
||||
|
||||
b.HasIndex("ParentSessionId")
|
||||
.HasDatabaseName("ix_auth_sessions_parent_session_id");
|
||||
|
||||
b.ToTable("auth_sessions", (string)null);
|
||||
});
|
||||
@@ -1317,12 +1457,6 @@ namespace DysonNetwork.Pass.Migrations
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("affected_at");
|
||||
|
||||
b.Property<string>("Area")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("character varying(1024)")
|
||||
.HasColumnName("area");
|
||||
|
||||
b.Property<Instant>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("created_at");
|
||||
@@ -1345,6 +1479,10 @@ namespace DysonNetwork.Pass.Migrations
|
||||
.HasColumnType("character varying(1024)")
|
||||
.HasColumnName("key");
|
||||
|
||||
b.Property<int>("Type")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("type");
|
||||
|
||||
b.Property<Instant>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("updated_at");
|
||||
@@ -1360,8 +1498,8 @@ namespace DysonNetwork.Pass.Migrations
|
||||
b.HasIndex("GroupId")
|
||||
.HasDatabaseName("ix_permission_nodes_group_id");
|
||||
|
||||
b.HasIndex("Key", "Area", "Actor")
|
||||
.HasDatabaseName("ix_permission_nodes_key_area_actor");
|
||||
b.HasIndex("Key", "Actor")
|
||||
.HasDatabaseName("ix_permission_nodes_key_actor");
|
||||
|
||||
b.ToTable("permission_nodes", (string)null);
|
||||
});
|
||||
@@ -1565,6 +1703,51 @@ namespace DysonNetwork.Pass.Migrations
|
||||
b.ToTable("realm_members", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Shared.Models.SnRewindPoint", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<Guid>("AccountId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("account_id");
|
||||
|
||||
b.Property<Instant>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("created_at");
|
||||
|
||||
b.Property<Dictionary<string, object>>("Data")
|
||||
.IsRequired()
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("data");
|
||||
|
||||
b.Property<Instant?>("DeletedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("deleted_at");
|
||||
|
||||
b.Property<int>("SchemaVersion")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("schema_version");
|
||||
|
||||
b.Property<Instant>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("updated_at");
|
||||
|
||||
b.Property<int>("Year")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("year");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_rewind_points");
|
||||
|
||||
b.HasIndex("AccountId")
|
||||
.HasDatabaseName("ix_rewind_points_account_id");
|
||||
|
||||
b.ToTable("rewind_points", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Shared.Models.SnSocialCreditRecord", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
@@ -1715,6 +1898,10 @@ namespace DysonNetwork.Pass.Migrations
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<int>("AmountOfSplits")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("amount_of_splits");
|
||||
|
||||
b.Property<Instant>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("created_at");
|
||||
@@ -2343,6 +2530,28 @@ namespace DysonNetwork.Pass.Migrations
|
||||
b.Navigation("Account");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Shared.Models.SnAffiliationResult", b =>
|
||||
{
|
||||
b.HasOne("DysonNetwork.Shared.Models.SnAffiliationSpell", "Spell")
|
||||
.WithMany()
|
||||
.HasForeignKey("SpellId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_affiliation_results_affiliation_spells_spell_id");
|
||||
|
||||
b.Navigation("Spell");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Shared.Models.SnAffiliationSpell", b =>
|
||||
{
|
||||
b.HasOne("DysonNetwork.Shared.Models.SnAccount", "Account")
|
||||
.WithMany()
|
||||
.HasForeignKey("AccountId")
|
||||
.HasConstraintName("fk_affiliation_spells_accounts_account_id");
|
||||
|
||||
b.Navigation("Account");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Shared.Models.SnApiKey", b =>
|
||||
{
|
||||
b.HasOne("DysonNetwork.Shared.Models.SnAccount", "Account")
|
||||
@@ -2373,14 +2582,7 @@ namespace DysonNetwork.Pass.Migrations
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_auth_challenges_accounts_account_id");
|
||||
|
||||
b.HasOne("DysonNetwork.Shared.Models.SnAuthClient", "Client")
|
||||
.WithMany()
|
||||
.HasForeignKey("ClientId")
|
||||
.HasConstraintName("fk_auth_challenges_auth_clients_client_id");
|
||||
|
||||
b.Navigation("Account");
|
||||
|
||||
b.Navigation("Client");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Shared.Models.SnAuthClient", b =>
|
||||
@@ -2404,14 +2606,21 @@ namespace DysonNetwork.Pass.Migrations
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_auth_sessions_accounts_account_id");
|
||||
|
||||
b.HasOne("DysonNetwork.Shared.Models.SnAuthChallenge", "Challenge")
|
||||
b.HasOne("DysonNetwork.Shared.Models.SnAuthClient", "Client")
|
||||
.WithMany()
|
||||
.HasForeignKey("ChallengeId")
|
||||
.HasConstraintName("fk_auth_sessions_auth_challenges_challenge_id");
|
||||
.HasForeignKey("ClientId")
|
||||
.HasConstraintName("fk_auth_sessions_auth_clients_client_id");
|
||||
|
||||
b.HasOne("DysonNetwork.Shared.Models.SnAuthSession", "ParentSession")
|
||||
.WithMany()
|
||||
.HasForeignKey("ParentSessionId")
|
||||
.HasConstraintName("fk_auth_sessions_auth_sessions_parent_session_id");
|
||||
|
||||
b.Navigation("Account");
|
||||
|
||||
b.Navigation("Challenge");
|
||||
b.Navigation("Client");
|
||||
|
||||
b.Navigation("ParentSession");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Shared.Models.SnCheckInResult", b =>
|
||||
@@ -2506,6 +2715,18 @@ namespace DysonNetwork.Pass.Migrations
|
||||
b.Navigation("Realm");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Shared.Models.SnRewindPoint", b =>
|
||||
{
|
||||
b.HasOne("DysonNetwork.Shared.Models.SnAccount", "Account")
|
||||
.WithMany()
|
||||
.HasForeignKey("AccountId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_rewind_points_accounts_account_id");
|
||||
|
||||
b.Navigation("Account");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Shared.Models.SnSocialCreditRecord", b =>
|
||||
{
|
||||
b.HasOne("DysonNetwork.Shared.Models.SnAccount", "Account")
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user