♻️ I have no idea what I have done

This commit is contained in:
2025-07-13 21:51:16 +08:00
parent afdbde951c
commit 03e26ef93c
29 changed files with 5933 additions and 645 deletions

View File

@@ -5,11 +5,13 @@ using DysonNetwork.Pass.Email;
using DysonNetwork.Pass.Localization;
using DysonNetwork.Pass.Permission;
using DysonNetwork.Shared.Cache;
using DysonNetwork.Shared.Proto;
using EFCore.BulkExtensions;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Localization;
using NodaTime;
using OtpNet;
using AuthSession = DysonNetwork.Pass.Auth.AuthSession;
namespace DysonNetwork.Pass.Account;
@@ -17,8 +19,8 @@ public class AccountService(
AppDatabase db,
MagicSpellService spells,
AccountUsernameService uname,
NotificationService nty,
EmailService mailer,
PusherService.PusherServiceClient pusher,
IStringLocalizer<NotificationResource> localizer,
ICacheService cache,
ILogger<AccountService> logger
@@ -353,13 +355,18 @@ public class AccountService(
if (await _GetFactorCode(factor) is not null)
throw new InvalidOperationException("A factor code has been sent and in active duration.");
await nty.SendNotification(
account,
"auth.verification",
localizer["AuthCodeTitle"],
null,
localizer["AuthCodeBody", code],
save: true
await pusher.SendPushNotificationToUserAsync(
new SendPushNotificationToUserRequest
{
UserId = account.Id.ToString(),
Notification = new PushNotification
{
Topic = "auth.verification",
Title = localizer["AuthCodeTitle"],
Body = localizer["AuthCodeBody", code],
IsSavable = false
}
}
);
await _SetFactorCode(factor, code, TimeSpan.FromMinutes(5));
break;
@@ -489,7 +496,7 @@ public class AccountService(
.ToListAsync();
if (session.Challenge.DeviceId is not null)
await nty.UnsubscribePushNotifications(session.Challenge.DeviceId);
await pusher.UnsubscribePushNotifications(session.Challenge.DeviceId);
// The current session should be included in the sessions' list
await db.AuthSessions

View File

@@ -1,167 +0,0 @@
using System.ComponentModel.DataAnnotations;
using DysonNetwork.Pass;
using DysonNetwork.Pass.Auth;
using DysonNetwork.Pass.Permission;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using NodaTime;
namespace DysonNetwork.Pass.Account;
[ApiController]
[Route("/api/notifications")]
public class NotificationController(AppDatabase db, NotificationService nty) : ControllerBase
{
[HttpGet("count")]
[Authorize]
public async Task<ActionResult<int>> CountUnreadNotifications()
{
HttpContext.Items.TryGetValue("CurrentUser", out var currentUserValue);
if (currentUserValue is not Account currentUser) return Unauthorized();
var count = await db.Notifications
.Where(s => s.AccountId == currentUser.Id && s.ViewedAt == null)
.CountAsync();
return Ok(count);
}
[HttpGet]
[Authorize]
public async Task<ActionResult<List<Notification>>> ListNotifications(
[FromQuery] int offset = 0,
// The page size set to 5 is to avoid the client pulled the notification
// but didn't render it in the screen-viewable region.
[FromQuery] int take = 5
)
{
HttpContext.Items.TryGetValue("CurrentUser", out var currentUserValue);
if (currentUserValue is not Account currentUser) return Unauthorized();
var totalCount = await db.Notifications
.Where(s => s.AccountId == currentUser.Id)
.CountAsync();
var notifications = await db.Notifications
.Where(s => s.AccountId == currentUser.Id)
.OrderByDescending(e => e.CreatedAt)
.Skip(offset)
.Take(take)
.ToListAsync();
Response.Headers["X-Total"] = totalCount.ToString();
await nty.MarkNotificationsViewed(notifications);
return Ok(notifications);
}
public class PushNotificationSubscribeRequest
{
[MaxLength(4096)] public string DeviceToken { get; set; } = null!;
public NotificationPushProvider Provider { get; set; }
}
[HttpPut("subscription")]
[Authorize]
public async Task<ActionResult<NotificationPushSubscription>> SubscribeToPushNotification(
[FromBody] PushNotificationSubscribeRequest request
)
{
HttpContext.Items.TryGetValue("CurrentSession", out var currentSessionValue);
HttpContext.Items.TryGetValue("CurrentUser", out var currentUserValue);
var currentUser = currentUserValue as Account;
if (currentUser == null) return Unauthorized();
var currentSession = currentSessionValue as AuthSession;
if (currentSession == null) return Unauthorized();
var result =
await nty.SubscribePushNotification(currentUser, request.Provider, currentSession.Challenge.DeviceId!,
request.DeviceToken);
return Ok(result);
}
[HttpDelete("subscription")]
[Authorize]
public async Task<ActionResult<int>> UnsubscribeFromPushNotification()
{
HttpContext.Items.TryGetValue("CurrentSession", out var currentSessionValue);
HttpContext.Items.TryGetValue("CurrentUser", out var currentUserValue);
var currentUser = currentUserValue as Account;
if (currentUser == null) return Unauthorized();
var currentSession = currentSessionValue as AuthSession;
if (currentSession == null) return Unauthorized();
var affectedRows = await db.NotificationPushSubscriptions
.Where(s =>
s.AccountId == currentUser.Id &&
s.DeviceId == currentSession.Challenge.DeviceId
).ExecuteDeleteAsync();
return Ok(affectedRows);
}
public class NotificationRequest
{
[Required] [MaxLength(1024)] public string Topic { get; set; } = null!;
[Required] [MaxLength(1024)] public string Title { get; set; } = null!;
[MaxLength(2048)] public string? Subtitle { get; set; }
[Required] [MaxLength(4096)] public string Content { get; set; } = null!;
public Dictionary<string, object>? Meta { get; set; }
public int Priority { get; set; } = 10;
}
[HttpPost("broadcast")]
[Authorize]
[RequiredPermission("global", "notifications.broadcast")]
public async Task<ActionResult> BroadcastNotification(
[FromBody] NotificationRequest request,
[FromQuery] bool save = false
)
{
await nty.BroadcastNotification(
new Notification
{
CreatedAt = SystemClock.Instance.GetCurrentInstant(),
UpdatedAt = SystemClock.Instance.GetCurrentInstant(),
Topic = request.Topic,
Title = request.Title,
Subtitle = request.Subtitle,
Content = request.Content,
Meta = request.Meta,
Priority = request.Priority,
},
save
);
return Ok();
}
public class NotificationWithAimRequest : NotificationRequest
{
[Required] public List<Guid> AccountId { get; set; } = null!;
}
[HttpPost("send")]
[Authorize]
[RequiredPermission("global", "notifications.send")]
public async Task<ActionResult> SendNotification(
[FromBody] NotificationWithAimRequest request,
[FromQuery] bool save = false
)
{
var accounts = await db.Accounts.Where(a => request.AccountId.Contains(a.Id)).ToListAsync();
await nty.SendNotificationBatch(
new Notification
{
CreatedAt = SystemClock.Instance.GetCurrentInstant(),
UpdatedAt = SystemClock.Instance.GetCurrentInstant(),
Topic = request.Topic,
Title = request.Title,
Subtitle = request.Subtitle,
Content = request.Content,
Meta = request.Meta,
},
accounts,
save
);
return Ok();
}
}

View File

@@ -1,292 +0,0 @@
using System.Text;
using System.Text.Json;
using DysonNetwork.Pass;
using EFCore.BulkExtensions;
using Microsoft.EntityFrameworkCore;
using NodaTime;
namespace DysonNetwork.Pass.Account;
public class NotificationService(
AppDatabase db,
IHttpClientFactory httpFactory,
IConfiguration config
)
{
private readonly string _notifyTopic = config["Notifications:Topic"]!;
private readonly Uri _notifyEndpoint = new(config["Notifications:Endpoint"]!);
public async Task UnsubscribePushNotifications(string deviceId)
{
await db.NotificationPushSubscriptions
.Where(s => s.DeviceId == deviceId)
.ExecuteDeleteAsync();
}
public async Task<NotificationPushSubscription> SubscribePushNotification(
Account account,
NotificationPushProvider provider,
string deviceId,
string deviceToken
)
{
var now = SystemClock.Instance.GetCurrentInstant();
// First check if a matching subscription exists
var existingSubscription = await db.NotificationPushSubscriptions
.Where(s => s.AccountId == account.Id)
.Where(s => s.DeviceId == deviceId || s.DeviceToken == deviceToken)
.FirstOrDefaultAsync();
if (existingSubscription is not null)
{
// Update the existing subscription directly in the database
await db.NotificationPushSubscriptions
.Where(s => s.Id == existingSubscription.Id)
.ExecuteUpdateAsync(setters => setters
.SetProperty(s => s.DeviceId, deviceId)
.SetProperty(s => s.DeviceToken, deviceToken)
.SetProperty(s => s.UpdatedAt, now));
// Return the updated subscription
existingSubscription.DeviceId = deviceId;
existingSubscription.DeviceToken = deviceToken;
existingSubscription.UpdatedAt = now;
return existingSubscription;
}
var subscription = new NotificationPushSubscription
{
DeviceId = deviceId,
DeviceToken = deviceToken,
Provider = provider,
AccountId = account.Id,
};
db.NotificationPushSubscriptions.Add(subscription);
await db.SaveChangesAsync();
return subscription;
}
public async Task<Notification> SendNotification(
Account account,
string topic,
string? title = null,
string? subtitle = null,
string? content = null,
Dictionary<string, object>? meta = null,
string? actionUri = null,
bool isSilent = false,
bool save = true
)
{
if (title is null && subtitle is null && content is null)
throw new ArgumentException("Unable to send notification that completely empty.");
meta ??= new Dictionary<string, object>();
if (actionUri is not null) meta["action_uri"] = actionUri;
var notification = new Notification
{
Topic = topic,
Title = title,
Subtitle = subtitle,
Content = content,
Meta = meta,
AccountId = account.Id,
};
if (save)
{
db.Add(notification);
await db.SaveChangesAsync();
}
if (!isSilent) _ = DeliveryNotification(notification);
return notification;
}
public async Task DeliveryNotification(Notification notification)
{
// Pushing the notification
var subscribers = await db.NotificationPushSubscriptions
.Where(s => s.AccountId == notification.AccountId)
.ToListAsync();
await _PushNotification(notification, subscribers);
}
public async Task MarkNotificationsViewed(ICollection<Notification> notifications)
{
var now = SystemClock.Instance.GetCurrentInstant();
var id = notifications.Where(n => n.ViewedAt == null).Select(n => n.Id).ToList();
if (id.Count == 0) return;
await db.Notifications
.Where(n => id.Contains(n.Id))
.ExecuteUpdateAsync(s => s.SetProperty(n => n.ViewedAt, now)
);
}
public async Task BroadcastNotification(Notification notification, bool save = false)
{
var accounts = await db.Accounts.ToListAsync();
if (save)
{
var notifications = accounts.Select(x =>
{
var newNotification = new Notification
{
Topic = notification.Topic,
Title = notification.Title,
Subtitle = notification.Subtitle,
Content = notification.Content,
Meta = notification.Meta,
Priority = notification.Priority,
Account = x,
AccountId = x.Id
};
return newNotification;
}).ToList();
await db.BulkInsertAsync(notifications);
}
foreach (var account in accounts)
{
notification.Account = account;
notification.AccountId = account.Id;
}
var subscribers = await db.NotificationPushSubscriptions
.ToListAsync();
await _PushNotification(notification, subscribers);
}
public async Task SendNotificationBatch(Notification notification, List<Account> accounts, bool save = false)
{
if (save)
{
var notifications = accounts.Select(x =>
{
var newNotification = new Notification
{
Topic = notification.Topic,
Title = notification.Title,
Subtitle = notification.Subtitle,
Content = notification.Content,
Meta = notification.Meta,
Priority = notification.Priority,
Account = x,
AccountId = x.Id
};
return newNotification;
}).ToList();
await db.BulkInsertAsync(notifications);
}
foreach (var account in accounts)
{
notification.Account = account;
notification.AccountId = account.Id;
}
var accountsId = accounts.Select(x => x.Id).ToList();
var subscribers = await db.NotificationPushSubscriptions
.Where(s => accountsId.Contains(s.AccountId))
.ToListAsync();
await _PushNotification(notification, subscribers);
}
private List<Dictionary<string, object>> _BuildNotificationPayload(Notification notification,
IEnumerable<NotificationPushSubscription> subscriptions)
{
var subDict = subscriptions
.GroupBy(x => x.Provider)
.ToDictionary(x => x.Key, x => x.ToList());
var notifications = subDict.Select(value =>
{
var platformCode = value.Key switch
{
NotificationPushProvider.Apple => 1,
NotificationPushProvider.Google => 2,
_ => throw new InvalidOperationException($"Unknown push provider: {value.Key}")
};
var tokens = value.Value.Select(x => x.DeviceToken).ToList();
return _BuildNotificationPayload(notification, platformCode, tokens);
}).ToList();
return notifications.ToList();
}
private Dictionary<string, object> _BuildNotificationPayload(Notification notification, int platformCode,
IEnumerable<string> deviceTokens)
{
var alertDict = new Dictionary<string, object>();
var dict = new Dictionary<string, object>
{
["notif_id"] = notification.Id.ToString(),
["apns_id"] = notification.Id.ToString(),
["topic"] = _notifyTopic,
["tokens"] = deviceTokens,
["data"] = new Dictionary<string, object>
{
["type"] = notification.Topic,
["meta"] = notification.Meta ?? new Dictionary<string, object>(),
},
["mutable_content"] = true,
["priority"] = notification.Priority >= 5 ? "high" : "normal",
};
if (!string.IsNullOrWhiteSpace(notification.Title))
{
dict["title"] = notification.Title;
alertDict["title"] = notification.Title;
}
if (!string.IsNullOrWhiteSpace(notification.Content))
{
dict["message"] = notification.Content;
alertDict["body"] = notification.Content;
}
if (!string.IsNullOrWhiteSpace(notification.Subtitle))
{
dict["message"] = $"{notification.Subtitle}\n{dict["message"]}";
alertDict["subtitle"] = notification.Subtitle;
}
if (notification.Priority >= 5)
dict["name"] = "default";
dict["platform"] = platformCode;
dict["alert"] = alertDict;
return dict;
}
private async Task _PushNotification(Notification notification,
IEnumerable<NotificationPushSubscription> subscriptions)
{
var subList = subscriptions.ToList();
if (subList.Count == 0) return;
var requestDict = new Dictionary<string, object>
{
["notifications"] = _BuildNotificationPayload(notification, subList)
};
var client = httpFactory.CreateClient();
client.BaseAddress = _notifyEndpoint;
var request = await client.PostAsync("/push", new StringContent(
JsonSerializer.Serialize(requestDict),
Encoding.UTF8,
"application/json"
));
request.EnsureSuccessStatusCode();
}
}

View File

@@ -9,6 +9,10 @@
<ItemGroup>
<PackageReference Include="Grpc.AspNetCore.Server" Version="2.71.0" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.7" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.7">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="NodaTime" Version="3.2.2" />
<PackageReference Include="NodaTime.Serialization.JsonNet" Version="3.2.0" />
<PackageReference Include="NodaTime.Serialization.Protobuf" Version="2.0.2" />

View File

@@ -1,33 +1,23 @@
using dotnet_etcd;
using dotnet_etcd.interfaces;
using DysonNetwork.Shared.Proto;
using Microsoft.AspNetCore.Components;
namespace DysonNetwork.Pass.Email;
public class EmailService
public class EmailService(
IEtcdClient etcd,
RazorViewRenderer viewRenderer,
IConfiguration configuration,
ILogger<EmailService> logger
)
{
private readonly PusherService.PusherServiceClient _client;
private readonly RazorViewRenderer _viewRenderer;
private readonly ILogger<EmailService> _logger;
private readonly PusherService.PusherServiceClient _client = GrpcClientHelper.CreatePusherServiceClient(
etcd,
configuration["Service:CertPath"]!,
configuration["Service:KeyPath"]!
).GetAwaiter().GetResult();
public EmailService(
EtcdClient etcd,
RazorViewRenderer viewRenderer,
IConfiguration configuration,
ILogger<EmailService> logger,
PusherService.PusherServiceClient client
)
{
_client = GrpcClientHelper.CreatePusherServiceClient(
etcd,
configuration["Service:CertPath"]!,
configuration["Service:KeyPath"]!
).GetAwaiter().GetResult();
_viewRenderer = viewRenderer;
_logger = logger;
_client = client;
}
public async Task SendEmailAsync(
string? recipientName,
string recipientEmail,
@@ -57,12 +47,12 @@ public class EmailService
{
try
{
var htmlBody = await _viewRenderer.RenderComponentToStringAsync<TComponent, TModel>(model);
var htmlBody = await viewRenderer.RenderComponentToStringAsync<TComponent, TModel>(model);
await SendEmailAsync(recipientName, recipientEmail, subject, htmlBody);
}
catch (Exception err)
{
_logger.LogError(err, "Failed to render email template...");
logger.LogError(err, "Failed to render email template...");
throw;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,998 @@
using System;
using System.Collections.Generic;
using System.Text.Json;
using DysonNetwork.Pass.Account;
using DysonNetwork.Pass.Developer;
using DysonNetwork.Pass.Wallet;
using DysonNetwork.Shared.Data;
using Microsoft.EntityFrameworkCore.Migrations;
using NetTopologySuite.Geometries;
using NodaTime;
#nullable disable
namespace DysonNetwork.Pass.Migrations
{
/// <inheritdoc />
public partial class InitialMigration : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterDatabase()
.Annotation("Npgsql:PostgresExtension:postgis", ",,");
migrationBuilder.CreateTable(
name: "accounts",
columns: table => new
{
id = table.Column<Guid>(type: "uuid", nullable: false),
name = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
nick = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
language = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: false),
activated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
is_superuser = table.Column<bool>(type: "boolean", nullable: false),
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("pk_accounts", x => x.id);
});
migrationBuilder.CreateTable(
name: "custom_apps",
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<CloudFileReferenceObject>(type: "jsonb", nullable: true),
background = table.Column<CloudFileReferenceObject>(type: "jsonb", nullable: true),
verification = table.Column<VerificationMark>(type: "jsonb", nullable: true),
oauth_config = table.Column<CustomAppOauthConfig>(type: "jsonb", nullable: true),
links = table.Column<CustomAppLinks>(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_custom_apps", x => x.id);
});
migrationBuilder.CreateTable(
name: "permission_groups",
columns: table => new
{
id = table.Column<Guid>(type: "uuid", nullable: false),
key = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false),
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("pk_permission_groups", x => x.id);
});
migrationBuilder.CreateTable(
name: "wallet_coupons",
columns: table => new
{
id = table.Column<Guid>(type: "uuid", nullable: false),
identifier = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: true),
code = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: true),
affected_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
expired_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
discount_amount = table.Column<decimal>(type: "numeric", nullable: true),
discount_rate = table.Column<double>(type: "double precision", nullable: true),
max_usage = 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_wallet_coupons", x => x.id);
});
migrationBuilder.CreateTable(
name: "abuse_reports",
columns: table => new
{
id = table.Column<Guid>(type: "uuid", nullable: false),
resource_identifier = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: false),
type = table.Column<int>(type: "integer", nullable: false),
reason = table.Column<string>(type: "character varying(8192)", maxLength: 8192, nullable: false),
resolved_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
resolution = table.Column<string>(type: "character varying(8192)", maxLength: 8192, 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_abuse_reports", x => x.id);
table.ForeignKey(
name: "fk_abuse_reports_accounts_account_id",
column: x => x.account_id,
principalTable: "accounts",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "account_auth_factors",
columns: table => new
{
id = table.Column<Guid>(type: "uuid", nullable: false),
type = table.Column<int>(type: "integer", nullable: false),
secret = table.Column<string>(type: "character varying(8196)", maxLength: 8196, nullable: true),
config = table.Column<Dictionary<string, object>>(type: "jsonb", nullable: true),
trustworthy = table.Column<int>(type: "integer", nullable: false),
enabled_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
expired_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
account_id = table.Column<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_account_auth_factors", x => x.id);
table.ForeignKey(
name: "fk_account_auth_factors_accounts_account_id",
column: x => x.account_id,
principalTable: "accounts",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "account_check_in_results",
columns: table => new
{
id = table.Column<Guid>(type: "uuid", nullable: false),
level = table.Column<int>(type: "integer", nullable: false),
reward_points = table.Column<decimal>(type: "numeric", nullable: true),
reward_experience = table.Column<int>(type: "integer", nullable: true),
tips = table.Column<ICollection<FortuneTip>>(type: "jsonb", 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_account_check_in_results", x => x.id);
table.ForeignKey(
name: "fk_account_check_in_results_accounts_account_id",
column: x => x.account_id,
principalTable: "accounts",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "account_connections",
columns: table => new
{
id = table.Column<Guid>(type: "uuid", nullable: false),
provider = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: false),
provided_identifier = table.Column<string>(type: "character varying(8192)", maxLength: 8192, nullable: false),
meta = table.Column<Dictionary<string, object>>(type: "jsonb", nullable: true),
access_token = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: true),
refresh_token = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: true),
last_used_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_account_connections", x => x.id);
table.ForeignKey(
name: "fk_account_connections_accounts_account_id",
column: x => x.account_id,
principalTable: "accounts",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "account_contacts",
columns: table => new
{
id = table.Column<Guid>(type: "uuid", nullable: false),
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),
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),
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("pk_account_contacts", x => x.id);
table.ForeignKey(
name: "fk_account_contacts_accounts_account_id",
column: x => x.account_id,
principalTable: "accounts",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "account_profiles",
columns: table => new
{
id = table.Column<Guid>(type: "uuid", nullable: false),
first_name = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
middle_name = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
last_name = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
bio = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: true),
gender = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: true),
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),
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<VerificationMark>(type: "jsonb", nullable: true),
active_badge = table.Column<BadgeReferenceObject>(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),
picture = table.Column<CloudFileReferenceObject>(type: "jsonb", nullable: true),
background = table.Column<CloudFileReferenceObject>(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_account_profiles", x => x.id);
table.ForeignKey(
name: "fk_account_profiles_accounts_account_id",
column: x => x.account_id,
principalTable: "accounts",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "account_relationships",
columns: table => new
{
account_id = table.Column<Guid>(type: "uuid", nullable: false),
related_id = table.Column<Guid>(type: "uuid", nullable: false),
expired_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
status = table.Column<short>(type: "smallint", nullable: false),
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("pk_account_relationships", x => new { x.account_id, x.related_id });
table.ForeignKey(
name: "fk_account_relationships_accounts_account_id",
column: x => x.account_id,
principalTable: "accounts",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "fk_account_relationships_accounts_related_id",
column: x => x.related_id,
principalTable: "accounts",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "account_statuses",
columns: table => new
{
id = table.Column<Guid>(type: "uuid", nullable: false),
attitude = table.Column<int>(type: "integer", nullable: false),
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),
cleared_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_account_statuses", x => x.id);
table.ForeignKey(
name: "fk_account_statuses_accounts_account_id",
column: x => x.account_id,
principalTable: "accounts",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "action_logs",
columns: table => new
{
id = table.Column<Guid>(type: "uuid", nullable: false),
action = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: false),
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),
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),
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_action_logs", x => x.id);
table.ForeignKey(
name: "fk_action_logs_accounts_account_id",
column: x => x.account_id,
principalTable: "accounts",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "auth_challenges",
columns: table => new
{
id = table.Column<Guid>(type: "uuid", nullable: false),
expired_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
step_remain = table.Column<int>(type: "integer", nullable: false),
step_total = table.Column<int>(type: "integer", nullable: false),
failed_attempts = table.Column<int>(type: "integer", nullable: false),
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),
nonce = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: true),
location = table.Column<Point>(type: "geometry", 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_auth_challenges", x => x.id);
table.ForeignKey(
name: "fk_auth_challenges_accounts_account_id",
column: x => x.account_id,
principalTable: "accounts",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "badges",
columns: table => new
{
id = table.Column<Guid>(type: "uuid", nullable: false),
type = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false),
label = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: true),
caption = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: true),
meta = table.Column<Dictionary<string, object>>(type: "jsonb", nullable: false),
activated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
expired_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
account_id = table.Column<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_badges", x => x.id);
table.ForeignKey(
name: "fk_badges_accounts_account_id",
column: x => x.account_id,
principalTable: "accounts",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "magic_spells",
columns: table => new
{
id = table.Column<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_magic_spells", x => x.id);
table.ForeignKey(
name: "fk_magic_spells_accounts_account_id",
column: x => x.account_id,
principalTable: "accounts",
principalColumn: "id");
});
migrationBuilder.CreateTable(
name: "notification_push_subscriptions",
columns: table => new
{
id = table.Column<Guid>(type: "uuid", nullable: false),
device_id = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: false),
device_token = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: false),
provider = table.Column<int>(type: "integer", nullable: false),
last_used_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
account_id = table.Column<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_notification_push_subscriptions", x => x.id);
table.ForeignKey(
name: "fk_notification_push_subscriptions_accounts_account_id",
column: x => x.account_id,
principalTable: "accounts",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "notifications",
columns: table => new
{
id = table.Column<Guid>(type: "uuid", nullable: false),
topic = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false),
title = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: true),
subtitle = table.Column<string>(type: "character varying(2048)", maxLength: 2048, nullable: true),
content = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: true),
meta = table.Column<Dictionary<string, object>>(type: "jsonb", nullable: true),
priority = table.Column<int>(type: "integer", nullable: false),
viewed_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
account_id = table.Column<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_notifications", x => x.id);
table.ForeignKey(
name: "fk_notifications_accounts_account_id",
column: x => x.account_id,
principalTable: "accounts",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "wallets",
columns: table => new
{
id = table.Column<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),
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_wallets", x => x.id);
table.ForeignKey(
name: "fk_wallets_accounts_account_id",
column: x => x.account_id,
principalTable: "accounts",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "custom_app_secrets",
columns: table => new
{
id = table.Column<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
{
group_id = table.Column<Guid>(type: "uuid", nullable: false),
actor = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false),
expired_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
affected_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("pk_permission_group_members", x => new { x.group_id, x.actor });
table.ForeignKey(
name: "fk_permission_group_members_permission_groups_group_id",
column: x => x.group_id,
principalTable: "permission_groups",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "permission_nodes",
columns: table => new
{
id = table.Column<Guid>(type: "uuid", nullable: false),
actor = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false),
area = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false),
key = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false),
value = table.Column<JsonDocument>(type: "jsonb", nullable: false),
expired_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
affected_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
group_id = table.Column<Guid>(type: "uuid", nullable: true),
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("pk_permission_nodes", x => x.id);
table.ForeignKey(
name: "fk_permission_nodes_permission_groups_group_id",
column: x => x.group_id,
principalTable: "permission_groups",
principalColumn: "id");
});
migrationBuilder.CreateTable(
name: "wallet_subscriptions",
columns: table => new
{
id = table.Column<Guid>(type: "uuid", nullable: false),
begun_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
ended_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
identifier = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: false),
is_active = table.Column<bool>(type: "boolean", nullable: false),
is_free_trial = table.Column<bool>(type: "boolean", nullable: false),
status = table.Column<int>(type: "integer", nullable: false),
payment_method = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: false),
payment_details = table.Column<PaymentDetails>(type: "jsonb", nullable: false),
base_price = table.Column<decimal>(type: "numeric", nullable: false),
coupon_id = table.Column<Guid>(type: "uuid", nullable: true),
renewal_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_wallet_subscriptions", x => x.id);
table.ForeignKey(
name: "fk_wallet_subscriptions_accounts_account_id",
column: x => x.account_id,
principalTable: "accounts",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "fk_wallet_subscriptions_wallet_coupons_coupon_id",
column: x => x.coupon_id,
principalTable: "wallet_coupons",
principalColumn: "id");
});
migrationBuilder.CreateTable(
name: "auth_sessions",
columns: table => new
{
id = table.Column<Guid>(type: "uuid", nullable: false),
label = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: true),
last_granted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
expired_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
account_id = table.Column<Guid>(type: "uuid", nullable: false),
challenge_id = table.Column<Guid>(type: "uuid", nullable: false),
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),
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("pk_auth_sessions", x => x.id);
table.ForeignKey(
name: "fk_auth_sessions_accounts_account_id",
column: x => x.account_id,
principalTable: "accounts",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "fk_auth_sessions_auth_challenges_challenge_id",
column: x => x.challenge_id,
principalTable: "auth_challenges",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "fk_auth_sessions_custom_apps_app_id",
column: x => x.app_id,
principalTable: "custom_apps",
principalColumn: "id");
});
migrationBuilder.CreateTable(
name: "payment_transactions",
columns: table => new
{
id = table.Column<Guid>(type: "uuid", nullable: false),
currency = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: false),
amount = table.Column<decimal>(type: "numeric", nullable: false),
remarks = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: true),
type = table.Column<int>(type: "integer", nullable: false),
payer_wallet_id = table.Column<Guid>(type: "uuid", nullable: true),
payee_wallet_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_payment_transactions", x => x.id);
table.ForeignKey(
name: "fk_payment_transactions_wallets_payee_wallet_id",
column: x => x.payee_wallet_id,
principalTable: "wallets",
principalColumn: "id");
table.ForeignKey(
name: "fk_payment_transactions_wallets_payer_wallet_id",
column: x => x.payer_wallet_id,
principalTable: "wallets",
principalColumn: "id");
});
migrationBuilder.CreateTable(
name: "wallet_pockets",
columns: table => new
{
id = table.Column<Guid>(type: "uuid", nullable: false),
currency = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: false),
amount = table.Column<decimal>(type: "numeric", nullable: false),
wallet_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_wallet_pockets", x => x.id);
table.ForeignKey(
name: "fk_wallet_pockets_wallets_wallet_id",
column: x => x.wallet_id,
principalTable: "wallets",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "payment_orders",
columns: table => new
{
id = table.Column<Guid>(type: "uuid", nullable: false),
status = table.Column<int>(type: "integer", nullable: false),
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),
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),
payee_wallet_id = table.Column<Guid>(type: "uuid", nullable: true),
transaction_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_payment_orders", x => x.id);
table.ForeignKey(
name: "fk_payment_orders_payment_transactions_transaction_id",
column: x => x.transaction_id,
principalTable: "payment_transactions",
principalColumn: "id");
table.ForeignKey(
name: "fk_payment_orders_wallets_payee_wallet_id",
column: x => x.payee_wallet_id,
principalTable: "wallets",
principalColumn: "id");
});
migrationBuilder.CreateIndex(
name: "ix_abuse_reports_account_id",
table: "abuse_reports",
column: "account_id");
migrationBuilder.CreateIndex(
name: "ix_account_auth_factors_account_id",
table: "account_auth_factors",
column: "account_id");
migrationBuilder.CreateIndex(
name: "ix_account_check_in_results_account_id",
table: "account_check_in_results",
column: "account_id");
migrationBuilder.CreateIndex(
name: "ix_account_connections_account_id",
table: "account_connections",
column: "account_id");
migrationBuilder.CreateIndex(
name: "ix_account_contacts_account_id",
table: "account_contacts",
column: "account_id");
migrationBuilder.CreateIndex(
name: "ix_account_profiles_account_id",
table: "account_profiles",
column: "account_id",
unique: true);
migrationBuilder.CreateIndex(
name: "ix_account_relationships_related_id",
table: "account_relationships",
column: "related_id");
migrationBuilder.CreateIndex(
name: "ix_account_statuses_account_id",
table: "account_statuses",
column: "account_id");
migrationBuilder.CreateIndex(
name: "ix_accounts_name",
table: "accounts",
column: "name",
unique: true);
migrationBuilder.CreateIndex(
name: "ix_action_logs_account_id",
table: "action_logs",
column: "account_id");
migrationBuilder.CreateIndex(
name: "ix_auth_challenges_account_id",
table: "auth_challenges",
column: "account_id");
migrationBuilder.CreateIndex(
name: "ix_auth_sessions_account_id",
table: "auth_sessions",
column: "account_id");
migrationBuilder.CreateIndex(
name: "ix_auth_sessions_app_id",
table: "auth_sessions",
column: "app_id");
migrationBuilder.CreateIndex(
name: "ix_auth_sessions_challenge_id",
table: "auth_sessions",
column: "challenge_id");
migrationBuilder.CreateIndex(
name: "ix_badges_account_id",
table: "badges",
column: "account_id");
migrationBuilder.CreateIndex(
name: "ix_custom_app_secrets_app_id",
table: "custom_app_secrets",
column: "app_id");
migrationBuilder.CreateIndex(
name: "ix_magic_spells_account_id",
table: "magic_spells",
column: "account_id");
migrationBuilder.CreateIndex(
name: "ix_magic_spells_spell",
table: "magic_spells",
column: "spell",
unique: true);
migrationBuilder.CreateIndex(
name: "ix_notification_push_subscriptions_account_id",
table: "notification_push_subscriptions",
column: "account_id");
migrationBuilder.CreateIndex(
name: "ix_notification_push_subscriptions_device_token_device_id_acco",
table: "notification_push_subscriptions",
columns: new[] { "device_token", "device_id", "account_id" },
unique: true);
migrationBuilder.CreateIndex(
name: "ix_notifications_account_id",
table: "notifications",
column: "account_id");
migrationBuilder.CreateIndex(
name: "ix_payment_orders_payee_wallet_id",
table: "payment_orders",
column: "payee_wallet_id");
migrationBuilder.CreateIndex(
name: "ix_payment_orders_transaction_id",
table: "payment_orders",
column: "transaction_id");
migrationBuilder.CreateIndex(
name: "ix_payment_transactions_payee_wallet_id",
table: "payment_transactions",
column: "payee_wallet_id");
migrationBuilder.CreateIndex(
name: "ix_payment_transactions_payer_wallet_id",
table: "payment_transactions",
column: "payer_wallet_id");
migrationBuilder.CreateIndex(
name: "ix_permission_nodes_group_id",
table: "permission_nodes",
column: "group_id");
migrationBuilder.CreateIndex(
name: "ix_permission_nodes_key_area_actor",
table: "permission_nodes",
columns: new[] { "key", "area", "actor" });
migrationBuilder.CreateIndex(
name: "ix_wallet_pockets_wallet_id",
table: "wallet_pockets",
column: "wallet_id");
migrationBuilder.CreateIndex(
name: "ix_wallet_subscriptions_account_id",
table: "wallet_subscriptions",
column: "account_id");
migrationBuilder.CreateIndex(
name: "ix_wallet_subscriptions_coupon_id",
table: "wallet_subscriptions",
column: "coupon_id");
migrationBuilder.CreateIndex(
name: "ix_wallet_subscriptions_identifier",
table: "wallet_subscriptions",
column: "identifier");
migrationBuilder.CreateIndex(
name: "ix_wallets_account_id",
table: "wallets",
column: "account_id");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "abuse_reports");
migrationBuilder.DropTable(
name: "account_auth_factors");
migrationBuilder.DropTable(
name: "account_check_in_results");
migrationBuilder.DropTable(
name: "account_connections");
migrationBuilder.DropTable(
name: "account_contacts");
migrationBuilder.DropTable(
name: "account_profiles");
migrationBuilder.DropTable(
name: "account_relationships");
migrationBuilder.DropTable(
name: "account_statuses");
migrationBuilder.DropTable(
name: "action_logs");
migrationBuilder.DropTable(
name: "auth_sessions");
migrationBuilder.DropTable(
name: "badges");
migrationBuilder.DropTable(
name: "custom_app_secrets");
migrationBuilder.DropTable(
name: "magic_spells");
migrationBuilder.DropTable(
name: "notification_push_subscriptions");
migrationBuilder.DropTable(
name: "notifications");
migrationBuilder.DropTable(
name: "payment_orders");
migrationBuilder.DropTable(
name: "permission_group_members");
migrationBuilder.DropTable(
name: "permission_nodes");
migrationBuilder.DropTable(
name: "wallet_pockets");
migrationBuilder.DropTable(
name: "wallet_subscriptions");
migrationBuilder.DropTable(
name: "auth_challenges");
migrationBuilder.DropTable(
name: "custom_apps");
migrationBuilder.DropTable(
name: "payment_transactions");
migrationBuilder.DropTable(
name: "permission_groups");
migrationBuilder.DropTable(
name: "wallet_coupons");
migrationBuilder.DropTable(
name: "wallets");
migrationBuilder.DropTable(
name: "accounts");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -28,8 +28,6 @@ builder.Services.AddAppBusinessServices(builder.Configuration);
// Add scheduled jobs
builder.Services.AddAppScheduledJobs();
builder.Services.AddHostedService<ServiceRegistrationHostedService>();
var app = builder.Build();
// Run database migrations

View File

@@ -1,56 +0,0 @@
using DysonNetwork.Shared.Registry;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System.Threading;
using System.Threading.Tasks;
namespace DysonNetwork.Pass.Startup;
public class ServiceRegistrationHostedService : IHostedService
{
private readonly ServiceRegistry _serviceRegistry;
private readonly IConfiguration _configuration;
private readonly ILogger<ServiceRegistrationHostedService> _logger;
public ServiceRegistrationHostedService(
ServiceRegistry serviceRegistry,
IConfiguration configuration,
ILogger<ServiceRegistrationHostedService> logger)
{
_serviceRegistry = serviceRegistry;
_configuration = configuration;
_logger = logger;
}
public async Task StartAsync(CancellationToken cancellationToken)
{
var serviceName = "DysonNetwork.Pass"; // Preset service name
var serviceUrl = _configuration["Service:Url"];
if (string.IsNullOrEmpty(serviceName) || string.IsNullOrEmpty(serviceUrl))
{
_logger.LogWarning("Service name or URL not configured. Skipping Etcd registration.");
return;
}
_logger.LogInformation("Registering service {ServiceName} at {ServiceUrl} with Etcd.", serviceName, serviceUrl);
try
{
await _serviceRegistry.RegisterService(serviceName, serviceUrl);
_logger.LogInformation("Service {ServiceName} registered successfully.", serviceName);
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to register service {ServiceName} with Etcd.", serviceName);
}
}
public Task StopAsync(CancellationToken cancellationToken)
{
// The lease will expire automatically if the service stops.
// For explicit unregistration, you would implement it here.
_logger.LogInformation("Service registration hosted service is stopping.");
return Task.CompletedTask;
}
}

View File

@@ -9,8 +9,9 @@
},
"AllowedHosts": "*",
"ConnectionStrings": {
"App": "Host=localhost;Port=5432;Database=dyson_network;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60",
"FastRetrieve": "localhost:6379"
"App": "Host=localhost;Port=5432;Database=dyson_pass;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60",
"FastRetrieve": "localhost:6379",
"Etcd": "etcd.orb.local:2379"
},
"Authentication": {
"Schemes": {
@@ -36,60 +37,11 @@
"AuthorizationCodeLifetime": "00:30:00",
"RequireHttpsMetadata": true
},
"Tus": {
"StorePath": "Uploads"
},
"Storage": {
"PreferredRemote": "minio",
"Remote": [
{
"Id": "minio",
"Label": "Minio",
"Region": "auto",
"Bucket": "solar-network-development",
"Endpoint": "localhost:9000",
"SecretId": "littlesheep",
"SecretKey": "password",
"EnabledSigned": true,
"EnableSsl": false
},
{
"Id": "cloudflare",
"Label": "Cloudflare R2",
"Region": "auto",
"Bucket": "solar-network",
"Endpoint": "0a70a6d1b7128888c823359d0008f4e1.r2.cloudflarestorage.com",
"SecretId": "8ff5d06c7b1639829d60bc6838a542e6",
"SecretKey": "fd58158c5201be16d1872c9209d9cf199421dae3c2f9972f94b2305976580d67",
"EnableSigned": true,
"EnableSsl": true
}
]
},
"Captcha": {
"Provider": "cloudflare",
"ApiKey": "0x4AAAAAABCDUdOujj4feOb_",
"ApiSecret": "0x4AAAAAABCDUWABiJQweqlB7tYq-IqIm8U"
},
"Notifications": {
"Topic": "dev.solsynth.solian",
"Endpoint": "http://localhost:8088"
},
"Email": {
"Server": "smtp4dev.orb.local",
"Port": 25,
"UseSsl": false,
"Username": "no-reply@mail.solsynth.dev",
"Password": "password",
"FromAddress": "no-reply@mail.solsynth.dev",
"FromName": "Alphabot",
"SubjectPrefix": "Solar Network"
},
"RealtimeChat": {
"Endpoint": "https://solar-network-im44o8gq.livekit.cloud",
"ApiKey": "APIs6TiL8wj3A4j",
"ApiSecret": "SffxRneIwTnlHPtEf3zicmmv3LUEl7xXael4PvWZrEhE"
},
"GeoIp": {
"DatabasePath": "./Keys/GeoLite2-City.mmdb"
},
@@ -125,5 +77,14 @@
"KnownProxies": [
"127.0.0.1",
"::1"
]
],
"Service": {
"Name": "DysonNetwork.Pass",
"Url": "http://localhost:5216",
"ClientCert": "../Certificates/client.crt",
"ClientKey": "../Certificates/client.key"
},
"Etcd": {
"Insecure": true
}
}