Compare commits

..

137 Commits

Author SHA1 Message Date
4443da5660 🐛 Fix sphere rewind 2025-12-26 01:16:29 +08:00
b193224a2c Add more data to rewind 2025-12-26 01:08:10 +08:00
4e9c5733d1 🐛 Fix sphere rewind InvalidOperationException 2025-12-25 23:31:29 +08:00
1c6b324b0d 🐛 Fix http client didn't ignore CA 2025-12-25 23:20:23 +08:00
ded3a70cb7 🐛 Fix rewind date issue 2025-12-25 23:14:40 +08:00
9e54b61eee 🐛 Fix account rewind service using wrong endpoint to call other services 2025-12-25 22:58:52 +08:00
43d89299c3 🗃️ Add rewind point migration 2025-12-25 22:51:59 +08:00
1af11b2a99 Account rewind controller 2025-12-25 22:47:01 +08:00
1a31d7cbe7 Chats in sphere rewind 2025-12-25 22:41:39 +08:00
f0d6772dca Pass rewind service and account rewind service 2025-12-25 22:26:57 +08:00
24836fc606 Rewind service basis and sphere service rewind 2025-12-25 21:36:26 +08:00
0bc77b948c 🚚 Rename the Stream to Queue in internal code 2025-12-25 19:11:39 +08:00
f792d43ab9 Fortune saying API 2025-12-24 23:51:48 +08:00
1b45be225a Notable days improvement (global days, recent days) 2025-12-24 23:32:14 +08:00
7811545726 💥 Update server readiness header 2025-12-24 23:07:57 +08:00
213608d4f0 Gateway readiness check 2025-12-24 22:09:03 +08:00
bca6a2ffde 🐛 Fix list members of chat and realm didn't show invite 2025-12-24 21:38:55 +08:00
885b895a3a 🐛 Fix DM permission check 2025-12-24 13:12:46 +08:00
08941a282b 🚚 Move the categories subscription listing API path 2025-12-24 00:10:07 +08:00
4fd455acbf 🐛 Fix the relationship listing order 2025-12-23 23:57:51 +08:00
5ff1539f18 🐛 Trying to fix the wrong relationship fetch 2025-12-23 23:56:24 +08:00
3c023a71b1 Subscription listing pagination & categories one 2025-12-23 23:34:14 +08:00
49d8eaa7b2 💥 Updated subscription API 2025-12-22 23:59:53 +08:00
16a37549fe 🐛 Fix publisher subscription status didn't include publisher 2025-12-22 23:40:09 +08:00
2aff62c64f 🐛 Fix chat room missing realms info 2025-12-21 22:39:59 +08:00
a49d485943 🚚 Use capitalized connection strings 2025-12-21 20:12:53 +08:00
4c65602465 ♻️ Update service discovery settings 2025-12-21 20:00:06 +08:00
4242953969 ♻️ Re-create the migrations for the Pass 2025-12-14 17:31:21 +08:00
c9530ac8b5 🚚 Rename GeoIP service 2025-12-14 03:19:08 +08:00
4ba7d38d78 📝 Update README 2025-12-14 03:14:40 +08:00
8642737a07 Configurable post page 2025-12-12 00:10:57 +08:00
8181938aaf Managed mode page will render with layout 2025-12-11 22:25:40 +08:00
922afc2239 🐛 Fix realm query 2025-12-10 22:59:18 +08:00
a071bd2738 Publication site global config data structure 2025-12-10 19:33:00 +08:00
43945fc524 🐛 Fix discovery realms order incorrect 2025-12-07 14:28:41 +08:00
e477429a35 👔 Increase the chance of other type of activities show up
🗑️ Remove debug include in timeline
2025-12-06 21:12:08 +08:00
fe3a057185 👔 Discovery realms will show desc by member count 2025-12-06 21:10:08 +08:00
ad3c104c5c Proper trace for auth session 2025-12-04 00:38:44 +08:00
2020d625aa 🗃️ Add migration of add sticker pack icon 2025-12-04 00:27:09 +08:00
f471c5635d Post article thumbnail 2025-12-04 00:26:54 +08:00
eaeaa28c60 Sticker icon 2025-12-04 00:19:36 +08:00
ee5c7cb7ce 🐛 Fix get device API 2025-12-03 23:29:31 +08:00
33abf12e41 🐛 Fix pass service swagger docs duplicate schema name cause 500 2025-12-03 22:46:47 +08:00
4a71f92ef0 ♻️ Updated auth challenges and device API to fit new design 2025-12-03 22:43:35 +08:00
4faa1a4b64 🐛 Fix message pack cache serilaize issue in sticker 2025-12-03 22:09:56 +08:00
e49a1ec49a Push token clean up when invalid 2025-12-03 21:42:18 +08:00
a88f42b26a Rolling back to old logic to provide mock device id in websocket gateway 2025-12-03 21:30:29 +08:00
c45be62331 Support switching from JSON to MessagePack in cache during runtime 2025-12-03 21:27:26 +08:00
c8228e0c8e Use JSON to serialize cache 2025-12-03 01:47:57 +08:00
c642c6d646 Resend self activation email API 2025-12-03 01:17:39 +08:00
270c211cb8 ♻️ Refactored to make a simplifier auth session system 2025-12-03 00:38:28 +08:00
74c8f3490d 🐛 Fix the message pack serializer 2025-12-03 00:38:12 +08:00
b364edc74b Use Json Serializer in cache again 2025-12-02 22:59:43 +08:00
9addf38677 🐛 Enable contractless serilization in cache to fix message pack serilizer 2025-12-02 22:51:12 +08:00
a02ed10434 🐛 Fix use wrong DI type in cache service 2025-12-02 22:45:30 +08:00
aca28f9318 ♻️ Refactored the cache service 2025-12-02 22:38:47 +08:00
c2f72993b7 🐛 Fix app snapshot didn't included in release 2025-12-02 21:52:24 +08:00
158cc75c5b 💥 Simplified permission node system and data structure 2025-12-02 21:42:26 +08:00
fa2f53ff7a 🐛 Fix file reference created with wrong date 2025-12-02 21:03:57 +08:00
2cce5ebf80 Use affiliation spell for registeration 2025-12-02 00:54:57 +08:00
13b2e46ecc Affliation spell CRUD 2025-12-01 23:33:48 +08:00
cbd68c9ae6 Proper site manager send file method 2025-12-01 22:55:20 +08:00
b99b61e0f9 🐛 Fix chat backward comapbility 2025-11-30 21:33:39 +08:00
94f4e68120 Timeout prevent send message logic 2025-11-30 21:13:54 +08:00
d5510f7e4d Chat timeout APIs
🐛 Fix member listing in chat
2025-11-30 21:08:07 +08:00
c038ab9e3c ♻️ A more robust and simpler chat system 2025-11-30 20:58:48 +08:00
e97719ec84 🗃️ Add missing account id migrations 2025-11-30 20:13:15 +08:00
40b8ea8eb8 🗃️ Bring account id back to chat room 2025-11-30 19:59:30 +08:00
f9b4dd45d7 🐛 Trying to fix relationship bugs 2025-11-30 17:52:19 +08:00
a46de4662c 🐛 Fix gateway 2025-11-30 17:51:27 +08:00
fdd14b860e 🐛 Fix wrong required status of validate account create request 2025-11-30 17:37:34 +08:00
cb62df81e2 👔 Adjust lookup account logic 2025-11-30 17:20:20 +08:00
46717e39a7 Admin delete account endpoint 2025-11-30 17:19:33 +08:00
344ed6e348 Account validation endpoint 2025-11-30 17:16:11 +08:00
a8b62fb0eb Auth via authorized device 2025-11-30 00:00:13 +08:00
00b3087d6a ♻️ Refactored auth service for better security 2025-11-29 18:00:23 +08:00
78f3873a0c 🐛 Fix birthday check in 2025-11-27 22:22:22 +08:00
a7f4173df7 Special birthday check in tips 2025-11-27 21:49:25 +08:00
f51c3c1724 🐛 Fix birthday check in result didn't show up 2025-11-27 21:41:30 +08:00
a92dc7e140 👔 Remove single file 1MB limit in site 2025-11-24 22:54:16 +08:00
c42befed6b ♻️ Refactored notification meta 2025-11-23 13:20:40 +08:00
2b95d58611 All unread messages endpoint 2025-11-23 12:28:57 +08:00
726a752fbb :zsap: Pagination in chat sync 2025-11-23 12:07:58 +08:00
2024972832 🐛 Trying to fix Pass service issues 2025-11-23 03:02:51 +08:00
d553ca2ca7 🐛 Dozens of bug fixes in chat 2025-11-23 01:17:15 +08:00
aeef16495f 🐛 Fix sitemap and rss still respond all types of posts 2025-11-22 18:55:29 +08:00
9b26a2a7eb 🐛 Fix replace of markdown convertion 2025-11-22 18:53:48 +08:00
2317033dae 👔 Stop rendering post attachments in article post on hosted pages 2025-11-22 18:24:32 +08:00
fd6e9c9780 🐛 Fix some stupid bugs 2025-11-22 18:22:53 +08:00
af0a2ff493 💄 Enrich post susbcription notification 2025-11-22 18:08:11 +08:00
b142a71c32 🐛 Fix publisher member didn't include publisher in response 2025-11-22 17:57:17 +08:00
27e3cc853a 🐛 Fix post service grpc call made type filter wrong 2025-11-22 17:55:45 +08:00
590519c28f 🐛 Fix index shows all type of posts in managed page 2025-11-22 17:53:52 +08:00
8ccf8100d4 👔 Make listing on the hosted page shows article only 2025-11-22 17:50:19 +08:00
ec21a94921 🐛 Serval bug fixes in hosted page 2025-11-22 17:43:52 +08:00
7b7a6c9218 Extend the ability of the hosted page markdown parser 2025-11-22 17:40:17 +08:00
0e44d9c514 🐛 Fix publisher invite controller still use int user id 2025-11-22 17:25:45 +08:00
e449e16d33 🐛 Fix pagination overflow in hosted page 2025-11-22 17:20:36 +08:00
3ce2b36c15 🐛 Fix featured post on hosted page uses wrong order 2025-11-22 17:13:02 +08:00
f7388822e0 🐛 Unable to use random split in open fund 2025-11-22 16:54:29 +08:00
3800dae8b7 SEO optimization on the hosted pages 2025-11-22 16:45:44 +08:00
c62ed191f3 File deploy smart mode 2025-11-22 16:00:30 +08:00
8b77f0e0ad Site management purge files and deploy from zip 2025-11-22 15:50:20 +08:00
2b56c6f1e5 Static site hosting support access directory as index.html 2025-11-22 15:49:29 +08:00
ef02265ccd 💄 Optimize hosted page index 2025-11-22 14:16:40 +08:00
f4505d2ecc 💄 Add titles to the hosted pages 2025-11-22 13:49:26 +08:00
9d2242d331 💄 Hosted page SEO optimization 2025-11-22 13:42:13 +08:00
c806365a81 Render markdown on hosted pages 2025-11-22 13:28:37 +08:00
bd1715c9a3 💄 Optimize hosted post details page 2025-11-22 13:17:34 +08:00
0b0598712e 💄 Updated the hosted site post page 2025-11-22 13:09:57 +08:00
92a4899e7c The posts page basis 2025-11-22 02:33:22 +08:00
bdc8db3091 About page also contains site info 2025-11-22 02:18:57 +08:00
a16da37221 Account about page 2025-11-22 01:47:10 +08:00
70a18b07ff 🐛 Bug fixes in the publication site hosting 2025-11-21 23:36:38 +08:00
98b8d5f33b ♻️ New error page 2025-11-21 23:30:43 +08:00
2a35786204 🐛 Fix self-managed files hosting 2025-11-21 22:27:27 +08:00
7016a0a943 Render self-managed site 2025-11-21 01:55:22 +08:00
cad72502d9 Managed page rendering 2025-11-21 01:41:25 +08:00
226a64df41 💄 Optimize the page rendering in zone 2025-11-21 01:21:40 +08:00
75b8567a28 🐛 Fix file management of the site 2025-11-21 00:40:58 +08:00
3aa5561a07 🐛 Fix hosted sites 2025-11-20 23:47:41 +08:00
c0ebb496fe Site manager 2025-11-20 22:54:24 +08:00
afccb27bd4 Site mode 2025-11-20 22:40:36 +08:00
6ed96780ab 💥 Improvements in the URL of the publication site 2025-11-20 21:29:32 +08:00
8e5cdfbc62 Zone site placeholder 2025-11-19 23:14:22 +08:00
1b774c1de6 ♻️ Moved the site to the Zone project 2025-11-19 22:34:01 +08:00
9b4cbade5c :heavy_plus_arrow: Add alpine.js to zone 2025-11-19 22:05:26 +08:00
a52e54f672 🔨 Setup the docker build for tailwindcss 2025-11-19 22:02:26 +08:00
aa48d5e25d 🔨 Setup the tailwindcss and daisyui frontend for the zone 2025-11-19 21:33:14 +08:00
ce18b194a5 🔨 Finish the initial setup of the Zone project 2025-11-19 21:12:07 +08:00
382579a20e 🎉 Initial commit for the Zone project 2025-11-19 21:04:44 +08:00
18d50346a9 👔 Update publication site limits for perk members 2025-11-19 00:48:36 +08:00
ac51bbde6c Publication Sites aka Solian Pages 2025-11-18 23:39:00 +08:00
4ab0dcf1c2 🐛 Fix file reference JSON loop 2025-11-18 21:52:21 +08:00
587066d847 Delete files in batch API 2025-11-18 20:33:02 +08:00
faa375042a New drive api order etc 2025-11-18 18:50:39 +08:00
65b6f3a606 🐛 Fix bugs 2025-11-18 18:40:23 +08:00
274 changed files with 22594 additions and 32887 deletions

View File

@@ -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

View File

@@ -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++)
{

View File

@@ -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>

View File

@@ -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();

View File

@@ -16,7 +16,7 @@ public static class ApplicationConfiguration
app.UseAuthentication();
app.UseAuthorization();
app.UseMiddleware<PermissionMiddleware>();
app.UseMiddleware<RemotePermissionMiddleware>();
app.MapControllers();

View File

@@ -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();

View File

@@ -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
}
}

View File

@@ -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!;

View File

@@ -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))

View File

@@ -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")

View File

@@ -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;

View File

@@ -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();

View File

@@ -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 });
}
}
}

View File

@@ -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)

View File

@@ -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);
}
}
}

View File

@@ -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

View File

@@ -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"
]
}

View 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"];
}

View 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);
}
}
}

View 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);
}
}

View 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()
);
}
}

View 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);
}
}

View File

@@ -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();

View File

@@ -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"
}
}

View File

@@ -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();

View File

@@ -19,6 +19,9 @@
"Etcd": {
"Insecure": true
},
"Cache": {
"Serializer": "MessagePack"
},
"Thinking": {
"DefaultService": "deepseek-chat",
"Services": {

View File

@@ -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();
}
}

View File

@@ -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

View File

@@ -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
{

View File

@@ -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);

View File

@@ -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();

View File

@@ -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)
{

View 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 });
}
}

View File

@@ -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();
}
}

View File

@@ -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
});
}

View File

@@ -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; } = [];

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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();

View File

@@ -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);
}
}

View 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();
}
}

View 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);
}
}

View File

@@ -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);

View File

@@ -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
{

View File

@@ -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 });
}
}

View File

@@ -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;
}
}

View File

@@ -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>
{

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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)
{

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

@@ -132,4 +132,8 @@
<AdditionalFiles Include="Resources\Emails\PasswordResetEmail.razor" />
<AdditionalFiles Include="Resources\Emails\RegistrationConfirmEmail.razor" />
</ItemGroup>
<ItemGroup>
<Folder Include="Migrations\" />
</ItemGroup>
</Project>

View File

@@ -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);

View File

@@ -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)

View File

@@ -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();

View File

@@ -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);
}
}
}

View File

@@ -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");
}
}
}

View File

@@ -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");
}
}
}

View File

@@ -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");
}
}
}

View File

@@ -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");
}
}
}

View File

@@ -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");
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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");
}
}
}

View File

@@ -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");
}
}
}

View File

@@ -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");
}
}
}

View File

@@ -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");
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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", ",,");
}
}
}

View File

@@ -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");
}
}
}

View File

@@ -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

View File

@@ -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");
}
}
}

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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");
}
}
}

View File

@@ -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");
}
}
}

View File

@@ -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");
}
}
}

View File

@@ -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");
}
}
}

View File

@@ -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 =>

View File

@@ -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");
}

View File

@@ -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