Compare commits
5 Commits
3080e273cb
...
82288fa52c
Author | SHA1 | Date | |
---|---|---|---|
82288fa52c | |||
e8c3219ef0 | |||
d343ac5fb8 | |||
bd7e589681 | |||
cb7179aa27 |
@ -1,5 +1,6 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
|
using DysonNetwork.Sphere.Permission;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using NodaTime;
|
using NodaTime;
|
||||||
|
|
||||||
@ -23,6 +24,8 @@ public class Account : ModelBase
|
|||||||
|
|
||||||
[JsonIgnore] public ICollection<Relationship> OutgoingRelationships { get; set; } = new List<Relationship>();
|
[JsonIgnore] public ICollection<Relationship> OutgoingRelationships { get; set; } = new List<Relationship>();
|
||||||
[JsonIgnore] public ICollection<Relationship> IncomingRelationships { get; set; } = new List<Relationship>();
|
[JsonIgnore] public ICollection<Relationship> IncomingRelationships { get; set; } = new List<Relationship>();
|
||||||
|
|
||||||
|
[JsonIgnore] public ICollection<PermissionGroupMember> GroupMemberships { get; set; } = new List<PermissionGroupMember>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Profile : ModelBase
|
public class Profile : ModelBase
|
||||||
|
42
DysonNetwork.Sphere/Account/Notification.cs
Normal file
42
DysonNetwork.Sphere/Account/Notification.cs
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using NodaTime;
|
||||||
|
|
||||||
|
namespace DysonNetwork.Sphere.Account;
|
||||||
|
|
||||||
|
public class Notification : ModelBase
|
||||||
|
{
|
||||||
|
public Guid Id { get; set; } = Guid.NewGuid();
|
||||||
|
[MaxLength(1024)] public string Topic { get; set; } = null!;
|
||||||
|
[MaxLength(1024)] public string? Title { get; set; }
|
||||||
|
[MaxLength(2048)] public string? Subtitle { get; set; }
|
||||||
|
[MaxLength(4096)] public string? Content { get; set; }
|
||||||
|
[Column(TypeName = "jsonb")] public Dictionary<string, object>? Meta { get; set; }
|
||||||
|
public int Priority { get; set; } = 10;
|
||||||
|
public Instant? ViewedAt { get; set; }
|
||||||
|
|
||||||
|
public long AccountId { get; set; }
|
||||||
|
[JsonIgnore] public Account Account { get; set; } = null!;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum NotificationPushProvider
|
||||||
|
{
|
||||||
|
Apple,
|
||||||
|
Google
|
||||||
|
}
|
||||||
|
|
||||||
|
[Index(nameof(DeviceId), IsUnique = true)]
|
||||||
|
[Index(nameof(DeviceToken), IsUnique = true)]
|
||||||
|
public class NotificationPushSubscription : ModelBase
|
||||||
|
{
|
||||||
|
public Guid Id { get; set; } = Guid.NewGuid();
|
||||||
|
[MaxLength(4096)] public string DeviceId { get; set; } = null!;
|
||||||
|
[MaxLength(4096)] public string DeviceToken { get; set; } = null!;
|
||||||
|
public NotificationPushProvider Provider { get; set; }
|
||||||
|
public Instant? LastUsedAt { get; set; }
|
||||||
|
|
||||||
|
public long AccountId { get; set; }
|
||||||
|
[JsonIgnore] public Account Account { get; set; } = null!;
|
||||||
|
}
|
59
DysonNetwork.Sphere/Account/NotificationController.cs
Normal file
59
DysonNetwork.Sphere/Account/NotificationController.cs
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using DysonNetwork.Sphere.Post;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace DysonNetwork.Sphere.Account;
|
||||||
|
|
||||||
|
[ApiController]
|
||||||
|
[Route("/notifications")]
|
||||||
|
public class NotificationController(AppDatabase db, NotificationService nty) : ControllerBase
|
||||||
|
{
|
||||||
|
[HttpGet]
|
||||||
|
[Authorize]
|
||||||
|
public async Task<ActionResult<List<Notification>>> ListNotifications([FromQuery] int offset = 0,
|
||||||
|
[FromQuery] int take = 20)
|
||||||
|
{
|
||||||
|
HttpContext.Items.TryGetValue("CurrentUser", out var currentUserValue);
|
||||||
|
var currentUser = currentUserValue as Account;
|
||||||
|
if (currentUser == null) 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();
|
||||||
|
|
||||||
|
return Ok(notifications);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PushNotificationSubscribeRequest
|
||||||
|
{
|
||||||
|
[MaxLength(4096)] public string DeviceId { get; set; } = null!;
|
||||||
|
[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("CurrentUser", out var currentUserValue);
|
||||||
|
var currentUser = currentUserValue as Account;
|
||||||
|
if (currentUser == null) return Unauthorized();
|
||||||
|
|
||||||
|
var result =
|
||||||
|
await nty.SubscribePushNotification(currentUser, request.Provider, request.DeviceId, request.DeviceToken);
|
||||||
|
|
||||||
|
return Ok(result);
|
||||||
|
}
|
||||||
|
}
|
184
DysonNetwork.Sphere/Account/NotificationService.cs
Normal file
184
DysonNetwork.Sphere/Account/NotificationService.cs
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
using CorePush.Apple;
|
||||||
|
using CorePush.Firebase;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using NodaTime;
|
||||||
|
|
||||||
|
namespace DysonNetwork.Sphere.Account;
|
||||||
|
|
||||||
|
public class NotificationService
|
||||||
|
{
|
||||||
|
private readonly AppDatabase _db;
|
||||||
|
private readonly ILogger<NotificationService> _logger;
|
||||||
|
private readonly FirebaseSender? _fcm;
|
||||||
|
private readonly ApnSender? _apns;
|
||||||
|
|
||||||
|
public NotificationService(
|
||||||
|
AppDatabase db,
|
||||||
|
IConfiguration cfg,
|
||||||
|
IHttpClientFactory clientFactory,
|
||||||
|
ILogger<NotificationService> logger
|
||||||
|
)
|
||||||
|
{
|
||||||
|
_db = db;
|
||||||
|
_logger = logger;
|
||||||
|
|
||||||
|
var cfgSection = cfg.GetSection("Notifications:Push");
|
||||||
|
|
||||||
|
// Set up the firebase push notification
|
||||||
|
var fcmConfig = cfgSection.GetValue<string>("Google");
|
||||||
|
if (fcmConfig != null)
|
||||||
|
_fcm = new FirebaseSender(File.ReadAllText(fcmConfig), clientFactory.CreateClient());
|
||||||
|
// Set up the apple push notification service
|
||||||
|
var apnsCert = cfgSection.GetValue<string>("Apple:PrivateKey");
|
||||||
|
if (apnsCert != null)
|
||||||
|
_apns = new ApnSender(new ApnSettings
|
||||||
|
{
|
||||||
|
P8PrivateKey = File.ReadAllText(apnsCert),
|
||||||
|
P8PrivateKeyId = cfgSection.GetValue<string>("Apple:PrivateKeyId"),
|
||||||
|
TeamId = cfgSection.GetValue<string>("Apple:TeamId"),
|
||||||
|
AppBundleIdentifier = cfgSection.GetValue<string>("Apple:BundleIdentifier"),
|
||||||
|
ServerType = cfgSection.GetValue<bool>("Production")
|
||||||
|
? ApnServerType.Production
|
||||||
|
: ApnServerType.Development
|
||||||
|
}, clientFactory.CreateClient());
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO remove all push notification with this device id when this device is logged out
|
||||||
|
|
||||||
|
public async Task<NotificationPushSubscription> SubscribePushNotification(
|
||||||
|
Account account,
|
||||||
|
NotificationPushProvider provider,
|
||||||
|
string deviceId,
|
||||||
|
string deviceToken
|
||||||
|
)
|
||||||
|
{
|
||||||
|
var existingSubscription = await _db.NotificationPushSubscriptions
|
||||||
|
.Where(s => s.AccountId == account.Id)
|
||||||
|
.Where(s => s.DeviceId == deviceId || s.DeviceToken == deviceToken)
|
||||||
|
.FirstOrDefaultAsync();
|
||||||
|
|
||||||
|
if (existingSubscription != null)
|
||||||
|
{
|
||||||
|
// Reset these audit fields to renew the lifecycle of this device token
|
||||||
|
existingSubscription.CreatedAt = Instant.FromDateTimeUtc(DateTime.UtcNow);
|
||||||
|
existingSubscription.UpdatedAt = Instant.FromDateTimeUtc(DateTime.UtcNow);
|
||||||
|
_db.Update(existingSubscription);
|
||||||
|
await _db.SaveChangesAsync();
|
||||||
|
return existingSubscription;
|
||||||
|
}
|
||||||
|
|
||||||
|
var subscription = new NotificationPushSubscription
|
||||||
|
{
|
||||||
|
DeviceId = deviceId,
|
||||||
|
DeviceToken = deviceToken,
|
||||||
|
Provider = provider,
|
||||||
|
Account = account,
|
||||||
|
AccountId = account.Id,
|
||||||
|
};
|
||||||
|
|
||||||
|
_db.Add(subscription);
|
||||||
|
await _db.SaveChangesAsync();
|
||||||
|
|
||||||
|
return subscription;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Notification> SendNotification(
|
||||||
|
Account account,
|
||||||
|
string? title = null,
|
||||||
|
string? subtitle = null,
|
||||||
|
string? content = null,
|
||||||
|
Dictionary<string, object>? meta = null,
|
||||||
|
bool isSilent = false
|
||||||
|
)
|
||||||
|
{
|
||||||
|
if (title is null && subtitle is null && content is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Unable to send notification that completely empty.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var notification = new Notification
|
||||||
|
{
|
||||||
|
Title = title,
|
||||||
|
Subtitle = subtitle,
|
||||||
|
Content = content,
|
||||||
|
Meta = meta,
|
||||||
|
Account = account,
|
||||||
|
AccountId = account.Id,
|
||||||
|
};
|
||||||
|
|
||||||
|
_db.Add(notification);
|
||||||
|
await _db.SaveChangesAsync();
|
||||||
|
|
||||||
|
#pragma warning disable CS4014
|
||||||
|
if (!isSilent) DeliveryNotification(notification);
|
||||||
|
#pragma warning restore CS4014
|
||||||
|
|
||||||
|
return notification;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task DeliveryNotification(Notification notification)
|
||||||
|
{
|
||||||
|
// TODO send websocket
|
||||||
|
|
||||||
|
// Pushing the notification
|
||||||
|
var subscribers = await _db.NotificationPushSubscriptions
|
||||||
|
.Where(s => s.AccountId == notification.AccountId)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
var tasks = new List<Task>();
|
||||||
|
foreach (var subscriber in subscribers)
|
||||||
|
{
|
||||||
|
tasks.Add(_PushSingleNotification(notification, subscriber));
|
||||||
|
}
|
||||||
|
|
||||||
|
await Task.WhenAll(tasks);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task _PushSingleNotification(Notification notification, NotificationPushSubscription subscription)
|
||||||
|
{
|
||||||
|
switch (subscription.Provider)
|
||||||
|
{
|
||||||
|
case NotificationPushProvider.Google:
|
||||||
|
if (_fcm == null)
|
||||||
|
throw new InvalidOperationException("The firebase cloud messaging is not initialized.");
|
||||||
|
await _fcm.SendAsync(new
|
||||||
|
{
|
||||||
|
message = new
|
||||||
|
{
|
||||||
|
token = subscription.DeviceToken,
|
||||||
|
notification = new
|
||||||
|
{
|
||||||
|
title = notification.Title,
|
||||||
|
body = string.Join("\n", notification.Subtitle, notification.Content),
|
||||||
|
},
|
||||||
|
data = notification.Meta
|
||||||
|
}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case NotificationPushProvider.Apple:
|
||||||
|
if (_apns == null)
|
||||||
|
throw new InvalidOperationException("The apple notification push service is not initialized.");
|
||||||
|
await _apns.SendAsync(new
|
||||||
|
{
|
||||||
|
apns = new
|
||||||
|
{
|
||||||
|
alert = new
|
||||||
|
{
|
||||||
|
title = notification.Title,
|
||||||
|
subtitle = notification.Subtitle,
|
||||||
|
content = notification.Content,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
meta = notification.Meta,
|
||||||
|
},
|
||||||
|
deviceToken: subscription.DeviceToken,
|
||||||
|
apnsId: notification.Id.ToString(),
|
||||||
|
apnsPriority: notification.Priority,
|
||||||
|
apnPushType: ApnPushType.Alert
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new InvalidOperationException($"Provider not supported: {subscription.Provider}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -19,6 +19,8 @@ public class AppDatabase(
|
|||||||
IConfiguration configuration
|
IConfiguration configuration
|
||||||
) : DbContext(options)
|
) : DbContext(options)
|
||||||
{
|
{
|
||||||
|
public DbSet<Permission.PermissionNode> PermissionNodes { get; set; } = null!;
|
||||||
|
public DbSet<Permission.PermissionGroup> PermissionGroups { get; set; } = null!;
|
||||||
public DbSet<Account.Account> Accounts { get; set; }
|
public DbSet<Account.Account> Accounts { get; set; }
|
||||||
public DbSet<Account.Profile> AccountProfiles { get; set; }
|
public DbSet<Account.Profile> AccountProfiles { get; set; }
|
||||||
public DbSet<Account.AccountContact> AccountContacts { get; set; }
|
public DbSet<Account.AccountContact> AccountContacts { get; set; }
|
||||||
@ -26,6 +28,8 @@ public class AppDatabase(
|
|||||||
public DbSet<Account.Relationship> AccountRelationships { get; set; }
|
public DbSet<Account.Relationship> AccountRelationships { get; set; }
|
||||||
public DbSet<Auth.Session> AuthSessions { get; set; }
|
public DbSet<Auth.Session> AuthSessions { get; set; }
|
||||||
public DbSet<Auth.Challenge> AuthChallenges { get; set; }
|
public DbSet<Auth.Challenge> AuthChallenges { get; set; }
|
||||||
|
public DbSet<Account.Notification> Notifications { get; set; }
|
||||||
|
public DbSet<Account.NotificationPushSubscription> NotificationPushSubscriptions { get; set; }
|
||||||
public DbSet<Storage.CloudFile> Files { get; set; }
|
public DbSet<Storage.CloudFile> Files { get; set; }
|
||||||
public DbSet<Post.Publisher> Publishers { get; set; }
|
public DbSet<Post.Publisher> Publishers { get; set; }
|
||||||
public DbSet<Post.PublisherMember> PublisherMembers { get; set; }
|
public DbSet<Post.PublisherMember> PublisherMembers { get; set; }
|
||||||
@ -54,6 +58,20 @@ public class AppDatabase(
|
|||||||
{
|
{
|
||||||
base.OnModelCreating(modelBuilder);
|
base.OnModelCreating(modelBuilder);
|
||||||
|
|
||||||
|
modelBuilder.Entity<Permission.PermissionGroupMember>()
|
||||||
|
.HasKey(pg => new { pg.GroupId, pg.AccountId })
|
||||||
|
.HasName("permission_group_members");
|
||||||
|
modelBuilder.Entity<Permission.PermissionGroupMember>()
|
||||||
|
.HasOne(pg => pg.Group)
|
||||||
|
.WithMany(g => g.Members)
|
||||||
|
.HasForeignKey(pg => pg.GroupId)
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
modelBuilder.Entity<Permission.PermissionGroupMember>()
|
||||||
|
.HasOne(pg => pg.Account)
|
||||||
|
.WithMany(a => a.GroupMemberships)
|
||||||
|
.HasForeignKey(pg => pg.AccountId)
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
|
||||||
modelBuilder.Entity<Account.Account>()
|
modelBuilder.Entity<Account.Account>()
|
||||||
.HasOne(a => a.Profile)
|
.HasOne(a => a.Profile)
|
||||||
.WithOne(p => p.Account)
|
.WithOne(p => p.Account)
|
||||||
|
@ -29,6 +29,7 @@ public class Challenge : ModelBase
|
|||||||
[MaxLength(256)] public string? DeviceId { get; set; }
|
[MaxLength(256)] public string? DeviceId { get; set; }
|
||||||
[MaxLength(1024)] public string? Nonce { get; set; }
|
[MaxLength(1024)] public string? Nonce { get; set; }
|
||||||
|
|
||||||
|
public long AccountId { get; set; }
|
||||||
[JsonIgnore] public Account.Account Account { get; set; } = null!;
|
[JsonIgnore] public Account.Account Account { get; set; } = null!;
|
||||||
|
|
||||||
public Challenge Normalize()
|
public Challenge Normalize()
|
||||||
|
50
DysonNetwork.Sphere/Connection/WebSocketController.cs
Normal file
50
DysonNetwork.Sphere/Connection/WebSocketController.cs
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
using System.Net.WebSockets;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace DysonNetwork.Sphere.Connection;
|
||||||
|
|
||||||
|
[ApiController]
|
||||||
|
[Route("/ws")]
|
||||||
|
public class WebSocketController : ControllerBase
|
||||||
|
{
|
||||||
|
[Route("/ws")]
|
||||||
|
[Authorize]
|
||||||
|
public async Task TheGateway()
|
||||||
|
{
|
||||||
|
if (HttpContext.WebSockets.IsWebSocketRequest)
|
||||||
|
{
|
||||||
|
using var webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync();
|
||||||
|
await _ConnectionEventLoop(webSocket);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
HttpContext.Response.StatusCode = StatusCodes.Status400BadRequest;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task _ConnectionEventLoop(WebSocket webSocket)
|
||||||
|
{
|
||||||
|
// For now, it's echo
|
||||||
|
var buffer = new byte[1024 * 4];
|
||||||
|
var receiveResult = await webSocket.ReceiveAsync(
|
||||||
|
new ArraySegment<byte>(buffer), CancellationToken.None);
|
||||||
|
|
||||||
|
while (!receiveResult.CloseStatus.HasValue)
|
||||||
|
{
|
||||||
|
await webSocket.SendAsync(
|
||||||
|
new ArraySegment<byte>(buffer, 0, receiveResult.Count),
|
||||||
|
receiveResult.MessageType,
|
||||||
|
receiveResult.EndOfMessage,
|
||||||
|
CancellationToken.None);
|
||||||
|
|
||||||
|
receiveResult = await webSocket.ReceiveAsync(
|
||||||
|
new ArraySegment<byte>(buffer), CancellationToken.None);
|
||||||
|
}
|
||||||
|
|
||||||
|
await webSocket.CloseAsync(
|
||||||
|
receiveResult.CloseStatus.Value,
|
||||||
|
receiveResult.CloseStatusDescription,
|
||||||
|
CancellationToken.None);
|
||||||
|
}
|
||||||
|
}
|
@ -13,6 +13,7 @@
|
|||||||
<PackageReference Include="Blurhash.ImageSharp" Version="4.0.0" />
|
<PackageReference Include="Blurhash.ImageSharp" Version="4.0.0" />
|
||||||
<PackageReference Include="Casbin.NET" Version="2.12.0" />
|
<PackageReference Include="Casbin.NET" Version="2.12.0" />
|
||||||
<PackageReference Include="Casbin.NET.Adapter.EFCore" Version="2.5.0" />
|
<PackageReference Include="Casbin.NET.Adapter.EFCore" Version="2.5.0" />
|
||||||
|
<PackageReference Include="CorePush" Version="4.3.0" />
|
||||||
<PackageReference Include="EFCore.NamingConventions" Version="9.0.0" />
|
<PackageReference Include="EFCore.NamingConventions" Version="9.0.0" />
|
||||||
<PackageReference Include="FFMpegCore" Version="5.2.0" />
|
<PackageReference Include="FFMpegCore" Version="5.2.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.4" />
|
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.4" />
|
||||||
|
1406
DysonNetwork.Sphere/Migrations/20250427154356_AddNotification.Designer.cs
generated
Normal file
1406
DysonNetwork.Sphere/Migrations/20250427154356_AddNotification.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
201
DysonNetwork.Sphere/Migrations/20250427154356_AddNotification.cs
Normal file
201
DysonNetwork.Sphere/Migrations/20250427154356_AddNotification.cs
Normal file
@ -0,0 +1,201 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using NodaTime;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace DysonNetwork.Sphere.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddNotification : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AlterColumn<string>(
|
||||||
|
name: "picture_id",
|
||||||
|
table: "publishers",
|
||||||
|
type: "character varying(128)",
|
||||||
|
nullable: true,
|
||||||
|
oldClrType: typeof(string),
|
||||||
|
oldType: "text",
|
||||||
|
oldNullable: true);
|
||||||
|
|
||||||
|
migrationBuilder.AlterColumn<string>(
|
||||||
|
name: "background_id",
|
||||||
|
table: "publishers",
|
||||||
|
type: "character varying(128)",
|
||||||
|
nullable: true,
|
||||||
|
oldClrType: typeof(string),
|
||||||
|
oldType: "text",
|
||||||
|
oldNullable: true);
|
||||||
|
|
||||||
|
migrationBuilder.AlterColumn<string>(
|
||||||
|
name: "id",
|
||||||
|
table: "files",
|
||||||
|
type: "character varying(128)",
|
||||||
|
maxLength: 128,
|
||||||
|
nullable: false,
|
||||||
|
oldClrType: typeof(string),
|
||||||
|
oldType: "text");
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<Instant>(
|
||||||
|
name: "expired_at",
|
||||||
|
table: "files",
|
||||||
|
type: "timestamp with time zone",
|
||||||
|
nullable: true);
|
||||||
|
|
||||||
|
migrationBuilder.AlterColumn<string>(
|
||||||
|
name: "picture_id",
|
||||||
|
table: "account_profiles",
|
||||||
|
type: "character varying(128)",
|
||||||
|
nullable: true,
|
||||||
|
oldClrType: typeof(string),
|
||||||
|
oldType: "text",
|
||||||
|
oldNullable: true);
|
||||||
|
|
||||||
|
migrationBuilder.AlterColumn<string>(
|
||||||
|
name: "background_id",
|
||||||
|
table: "account_profiles",
|
||||||
|
type: "character varying(128)",
|
||||||
|
nullable: true,
|
||||||
|
oldClrType: typeof(string),
|
||||||
|
oldType: "text",
|
||||||
|
oldNullable: true);
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "notification_push_subscriptions",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
device_id = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: false),
|
||||||
|
device_token = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: false),
|
||||||
|
provider = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
last_used_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
||||||
|
account_id = table.Column<long>(type: "bigint", nullable: false),
|
||||||
|
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||||
|
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||||
|
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("pk_notification_push_subscriptions", x => x.id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "fk_notification_push_subscriptions_accounts_account_id",
|
||||||
|
column: x => x.account_id,
|
||||||
|
principalTable: "accounts",
|
||||||
|
principalColumn: "id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "notifications",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
title = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: true),
|
||||||
|
subtitle = table.Column<string>(type: "character varying(2048)", maxLength: 2048, nullable: true),
|
||||||
|
content = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: true),
|
||||||
|
meta = table.Column<Dictionary<string, object>>(type: "jsonb", nullable: true),
|
||||||
|
priority = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
viewed_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
||||||
|
account_id = table.Column<long>(type: "bigint", nullable: false),
|
||||||
|
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||||
|
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||||
|
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("pk_notifications", x => x.id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "fk_notifications_accounts_account_id",
|
||||||
|
column: x => x.account_id,
|
||||||
|
principalTable: "accounts",
|
||||||
|
principalColumn: "id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "ix_notification_push_subscriptions_account_id",
|
||||||
|
table: "notification_push_subscriptions",
|
||||||
|
column: "account_id");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "ix_notification_push_subscriptions_device_id",
|
||||||
|
table: "notification_push_subscriptions",
|
||||||
|
column: "device_id",
|
||||||
|
unique: true);
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "ix_notification_push_subscriptions_device_token",
|
||||||
|
table: "notification_push_subscriptions",
|
||||||
|
column: "device_token",
|
||||||
|
unique: true);
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "ix_notifications_account_id",
|
||||||
|
table: "notifications",
|
||||||
|
column: "account_id");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "notification_push_subscriptions");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "notifications");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "expired_at",
|
||||||
|
table: "files");
|
||||||
|
|
||||||
|
migrationBuilder.AlterColumn<string>(
|
||||||
|
name: "picture_id",
|
||||||
|
table: "publishers",
|
||||||
|
type: "text",
|
||||||
|
nullable: true,
|
||||||
|
oldClrType: typeof(string),
|
||||||
|
oldType: "character varying(128)",
|
||||||
|
oldNullable: true);
|
||||||
|
|
||||||
|
migrationBuilder.AlterColumn<string>(
|
||||||
|
name: "background_id",
|
||||||
|
table: "publishers",
|
||||||
|
type: "text",
|
||||||
|
nullable: true,
|
||||||
|
oldClrType: typeof(string),
|
||||||
|
oldType: "character varying(128)",
|
||||||
|
oldNullable: true);
|
||||||
|
|
||||||
|
migrationBuilder.AlterColumn<string>(
|
||||||
|
name: "id",
|
||||||
|
table: "files",
|
||||||
|
type: "text",
|
||||||
|
nullable: false,
|
||||||
|
oldClrType: typeof(string),
|
||||||
|
oldType: "character varying(128)",
|
||||||
|
oldMaxLength: 128);
|
||||||
|
|
||||||
|
migrationBuilder.AlterColumn<string>(
|
||||||
|
name: "picture_id",
|
||||||
|
table: "account_profiles",
|
||||||
|
type: "text",
|
||||||
|
nullable: true,
|
||||||
|
oldClrType: typeof(string),
|
||||||
|
oldType: "character varying(128)",
|
||||||
|
oldNullable: true);
|
||||||
|
|
||||||
|
migrationBuilder.AlterColumn<string>(
|
||||||
|
name: "background_id",
|
||||||
|
table: "account_profiles",
|
||||||
|
type: "text",
|
||||||
|
nullable: true,
|
||||||
|
oldClrType: typeof(string),
|
||||||
|
oldType: "character varying(128)",
|
||||||
|
oldNullable: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
1600
DysonNetwork.Sphere/Migrations/20250427161252_AddPermission.Designer.cs
generated
Normal file
1600
DysonNetwork.Sphere/Migrations/20250427161252_AddPermission.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
137
DysonNetwork.Sphere/Migrations/20250427161252_AddPermission.cs
Normal file
137
DysonNetwork.Sphere/Migrations/20250427161252_AddPermission.cs
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using NodaTime;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace DysonNetwork.Sphere.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddPermission : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "topic",
|
||||||
|
table: "notifications",
|
||||||
|
type: "character varying(1024)",
|
||||||
|
maxLength: 1024,
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: "");
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "permission_groups",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
key = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false),
|
||||||
|
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||||
|
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||||
|
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("pk_permission_groups", x => x.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "permission_group_member",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
group_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
account_id = table.Column<long>(type: "bigint", nullable: false),
|
||||||
|
expired_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
||||||
|
affected_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
||||||
|
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||||
|
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||||
|
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("pk_permission_group_member", x => new { x.group_id, x.account_id });
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "fk_permission_group_member_accounts_account_id",
|
||||||
|
column: x => x.account_id,
|
||||||
|
principalTable: "accounts",
|
||||||
|
principalColumn: "id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "fk_permission_group_member_permission_groups_group_id",
|
||||||
|
column: x => x.group_id,
|
||||||
|
principalTable: "permission_groups",
|
||||||
|
principalColumn: "id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "permission_nodes",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
actor = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false),
|
||||||
|
area = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false),
|
||||||
|
key = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false),
|
||||||
|
value = table.Column<object>(type: "jsonb", nullable: false),
|
||||||
|
expired_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
||||||
|
affected_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
||||||
|
group_id = table.Column<Guid>(type: "uuid", nullable: true),
|
||||||
|
permission_group_id = table.Column<Guid>(type: "uuid", nullable: true),
|
||||||
|
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||||
|
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||||
|
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("pk_permission_nodes", x => x.id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "fk_permission_nodes_permission_groups_permission_group_id",
|
||||||
|
column: x => x.permission_group_id,
|
||||||
|
principalTable: "permission_groups",
|
||||||
|
principalColumn: "id");
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "fk_permission_nodes_permission_nodes_group_id",
|
||||||
|
column: x => x.group_id,
|
||||||
|
principalTable: "permission_nodes",
|
||||||
|
principalColumn: "id");
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "ix_permission_group_member_account_id",
|
||||||
|
table: "permission_group_member",
|
||||||
|
column: "account_id");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "ix_permission_nodes_group_id",
|
||||||
|
table: "permission_nodes",
|
||||||
|
column: "group_id");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "ix_permission_nodes_key_area_actor",
|
||||||
|
table: "permission_nodes",
|
||||||
|
columns: new[] { "key", "area", "actor" });
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "ix_permission_nodes_permission_group_id",
|
||||||
|
table: "permission_nodes",
|
||||||
|
column: "permission_group_id");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "permission_group_member");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "permission_nodes");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "permission_groups");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "topic",
|
||||||
|
table: "notifications");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -167,6 +167,131 @@ namespace DysonNetwork.Sphere.Migrations
|
|||||||
b.ToTable("account_contacts", (string)null);
|
b.ToTable("account_contacts", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DysonNetwork.Sphere.Account.Notification", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<long>("AccountId")
|
||||||
|
.HasColumnType("bigint")
|
||||||
|
.HasColumnName("account_id");
|
||||||
|
|
||||||
|
b.Property<string>("Content")
|
||||||
|
.HasMaxLength(4096)
|
||||||
|
.HasColumnType("character varying(4096)")
|
||||||
|
.HasColumnName("content");
|
||||||
|
|
||||||
|
b.Property<Instant>("CreatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("created_at");
|
||||||
|
|
||||||
|
b.Property<Instant?>("DeletedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("deleted_at");
|
||||||
|
|
||||||
|
b.Property<Dictionary<string, object>>("Meta")
|
||||||
|
.HasColumnType("jsonb")
|
||||||
|
.HasColumnName("meta");
|
||||||
|
|
||||||
|
b.Property<int>("Priority")
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasColumnName("priority");
|
||||||
|
|
||||||
|
b.Property<string>("Subtitle")
|
||||||
|
.HasMaxLength(2048)
|
||||||
|
.HasColumnType("character varying(2048)")
|
||||||
|
.HasColumnName("subtitle");
|
||||||
|
|
||||||
|
b.Property<string>("Title")
|
||||||
|
.HasMaxLength(1024)
|
||||||
|
.HasColumnType("character varying(1024)")
|
||||||
|
.HasColumnName("title");
|
||||||
|
|
||||||
|
b.Property<string>("Topic")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(1024)
|
||||||
|
.HasColumnType("character varying(1024)")
|
||||||
|
.HasColumnName("topic");
|
||||||
|
|
||||||
|
b.Property<Instant>("UpdatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("updated_at");
|
||||||
|
|
||||||
|
b.Property<Instant?>("ViewedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("viewed_at");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("pk_notifications");
|
||||||
|
|
||||||
|
b.HasIndex("AccountId")
|
||||||
|
.HasDatabaseName("ix_notifications_account_id");
|
||||||
|
|
||||||
|
b.ToTable("notifications", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DysonNetwork.Sphere.Account.NotificationPushSubscription", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<long>("AccountId")
|
||||||
|
.HasColumnType("bigint")
|
||||||
|
.HasColumnName("account_id");
|
||||||
|
|
||||||
|
b.Property<Instant>("CreatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("created_at");
|
||||||
|
|
||||||
|
b.Property<Instant?>("DeletedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("deleted_at");
|
||||||
|
|
||||||
|
b.Property<string>("DeviceId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(4096)
|
||||||
|
.HasColumnType("character varying(4096)")
|
||||||
|
.HasColumnName("device_id");
|
||||||
|
|
||||||
|
b.Property<string>("DeviceToken")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(4096)
|
||||||
|
.HasColumnType("character varying(4096)")
|
||||||
|
.HasColumnName("device_token");
|
||||||
|
|
||||||
|
b.Property<Instant?>("LastUsedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("last_used_at");
|
||||||
|
|
||||||
|
b.Property<int>("Provider")
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasColumnName("provider");
|
||||||
|
|
||||||
|
b.Property<Instant>("UpdatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("updated_at");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("pk_notification_push_subscriptions");
|
||||||
|
|
||||||
|
b.HasIndex("AccountId")
|
||||||
|
.HasDatabaseName("ix_notification_push_subscriptions_account_id");
|
||||||
|
|
||||||
|
b.HasIndex("DeviceId")
|
||||||
|
.IsUnique()
|
||||||
|
.HasDatabaseName("ix_notification_push_subscriptions_device_id");
|
||||||
|
|
||||||
|
b.HasIndex("DeviceToken")
|
||||||
|
.IsUnique()
|
||||||
|
.HasDatabaseName("ix_notification_push_subscriptions_device_token");
|
||||||
|
|
||||||
|
b.ToTable("notification_push_subscriptions", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("DysonNetwork.Sphere.Account.Profile", b =>
|
modelBuilder.Entity("DysonNetwork.Sphere.Account.Profile", b =>
|
||||||
{
|
{
|
||||||
b.Property<long>("Id")
|
b.Property<long>("Id")
|
||||||
@ -174,7 +299,7 @@ namespace DysonNetwork.Sphere.Migrations
|
|||||||
.HasColumnName("id");
|
.HasColumnName("id");
|
||||||
|
|
||||||
b.Property<string>("BackgroundId")
|
b.Property<string>("BackgroundId")
|
||||||
.HasColumnType("text")
|
.HasColumnType("character varying(128)")
|
||||||
.HasColumnName("background_id");
|
.HasColumnName("background_id");
|
||||||
|
|
||||||
b.Property<string>("Bio")
|
b.Property<string>("Bio")
|
||||||
@ -206,7 +331,7 @@ namespace DysonNetwork.Sphere.Migrations
|
|||||||
.HasColumnName("middle_name");
|
.HasColumnName("middle_name");
|
||||||
|
|
||||||
b.Property<string>("PictureId")
|
b.Property<string>("PictureId")
|
||||||
.HasColumnType("text")
|
.HasColumnType("character varying(128)")
|
||||||
.HasColumnName("picture_id");
|
.HasColumnName("picture_id");
|
||||||
|
|
||||||
b.Property<Instant>("UpdatedAt")
|
b.Property<Instant>("UpdatedAt")
|
||||||
@ -390,6 +515,149 @@ namespace DysonNetwork.Sphere.Migrations
|
|||||||
b.ToTable("auth_sessions", (string)null);
|
b.ToTable("auth_sessions", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroup", 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>("Key")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(1024)
|
||||||
|
.HasColumnType("character varying(1024)")
|
||||||
|
.HasColumnName("key");
|
||||||
|
|
||||||
|
b.Property<Instant>("UpdatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("updated_at");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("pk_permission_groups");
|
||||||
|
|
||||||
|
b.ToTable("permission_groups", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroupMember", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("GroupId")
|
||||||
|
.HasColumnType("uuid")
|
||||||
|
.HasColumnName("group_id");
|
||||||
|
|
||||||
|
b.Property<long>("AccountId")
|
||||||
|
.HasColumnType("bigint")
|
||||||
|
.HasColumnName("account_id");
|
||||||
|
|
||||||
|
b.Property<Instant?>("AffectedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("affected_at");
|
||||||
|
|
||||||
|
b.Property<Instant>("CreatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("created_at");
|
||||||
|
|
||||||
|
b.Property<Instant?>("DeletedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("deleted_at");
|
||||||
|
|
||||||
|
b.Property<Instant?>("ExpiredAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("expired_at");
|
||||||
|
|
||||||
|
b.Property<Instant>("UpdatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("updated_at");
|
||||||
|
|
||||||
|
b.HasKey("GroupId", "AccountId")
|
||||||
|
.HasName("pk_permission_group_member");
|
||||||
|
|
||||||
|
b.HasIndex("AccountId")
|
||||||
|
.HasDatabaseName("ix_permission_group_member_account_id");
|
||||||
|
|
||||||
|
b.ToTable("permission_group_member", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionNode", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<string>("Actor")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(1024)
|
||||||
|
.HasColumnType("character varying(1024)")
|
||||||
|
.HasColumnName("actor");
|
||||||
|
|
||||||
|
b.Property<Instant?>("AffectedAt")
|
||||||
|
.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");
|
||||||
|
|
||||||
|
b.Property<Instant?>("DeletedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("deleted_at");
|
||||||
|
|
||||||
|
b.Property<Instant?>("ExpiredAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("expired_at");
|
||||||
|
|
||||||
|
b.Property<Guid?>("GroupId")
|
||||||
|
.HasColumnType("uuid")
|
||||||
|
.HasColumnName("group_id");
|
||||||
|
|
||||||
|
b.Property<string>("Key")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(1024)
|
||||||
|
.HasColumnType("character varying(1024)")
|
||||||
|
.HasColumnName("key");
|
||||||
|
|
||||||
|
b.Property<Guid?>("PermissionGroupId")
|
||||||
|
.HasColumnType("uuid")
|
||||||
|
.HasColumnName("permission_group_id");
|
||||||
|
|
||||||
|
b.Property<Instant>("UpdatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("updated_at");
|
||||||
|
|
||||||
|
b.Property<object>("Value")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("jsonb")
|
||||||
|
.HasColumnName("value");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("pk_permission_nodes");
|
||||||
|
|
||||||
|
b.HasIndex("GroupId")
|
||||||
|
.HasDatabaseName("ix_permission_nodes_group_id");
|
||||||
|
|
||||||
|
b.HasIndex("PermissionGroupId")
|
||||||
|
.HasDatabaseName("ix_permission_nodes_permission_group_id");
|
||||||
|
|
||||||
|
b.HasIndex("Key", "Area", "Actor")
|
||||||
|
.HasDatabaseName("ix_permission_nodes_key_area_actor");
|
||||||
|
|
||||||
|
b.ToTable("permission_nodes", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("DysonNetwork.Sphere.Post.Post", b =>
|
modelBuilder.Entity("DysonNetwork.Sphere.Post.Post", b =>
|
||||||
{
|
{
|
||||||
b.Property<long>("Id")
|
b.Property<long>("Id")
|
||||||
@ -692,7 +960,7 @@ namespace DysonNetwork.Sphere.Migrations
|
|||||||
.HasColumnName("account_id");
|
.HasColumnName("account_id");
|
||||||
|
|
||||||
b.Property<string>("BackgroundId")
|
b.Property<string>("BackgroundId")
|
||||||
.HasColumnType("text")
|
.HasColumnType("character varying(128)")
|
||||||
.HasColumnName("background_id");
|
.HasColumnName("background_id");
|
||||||
|
|
||||||
b.Property<string>("Bio")
|
b.Property<string>("Bio")
|
||||||
@ -721,7 +989,7 @@ namespace DysonNetwork.Sphere.Migrations
|
|||||||
.HasColumnName("nick");
|
.HasColumnName("nick");
|
||||||
|
|
||||||
b.Property<string>("PictureId")
|
b.Property<string>("PictureId")
|
||||||
.HasColumnType("text")
|
.HasColumnType("character varying(128)")
|
||||||
.HasColumnName("picture_id");
|
.HasColumnName("picture_id");
|
||||||
|
|
||||||
b.Property<int>("PublisherType")
|
b.Property<int>("PublisherType")
|
||||||
@ -793,7 +1061,8 @@ namespace DysonNetwork.Sphere.Migrations
|
|||||||
modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFile", b =>
|
modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFile", b =>
|
||||||
{
|
{
|
||||||
b.Property<string>("Id")
|
b.Property<string>("Id")
|
||||||
.HasColumnType("text")
|
.HasMaxLength(128)
|
||||||
|
.HasColumnType("character varying(128)")
|
||||||
.HasColumnName("id");
|
.HasColumnName("id");
|
||||||
|
|
||||||
b.Property<long>("AccountId")
|
b.Property<long>("AccountId")
|
||||||
@ -813,6 +1082,10 @@ namespace DysonNetwork.Sphere.Migrations
|
|||||||
.HasColumnType("character varying(4096)")
|
.HasColumnType("character varying(4096)")
|
||||||
.HasColumnName("description");
|
.HasColumnName("description");
|
||||||
|
|
||||||
|
b.Property<Instant?>("ExpiredAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("expired_at");
|
||||||
|
|
||||||
b.Property<Dictionary<string, object>>("FileMeta")
|
b.Property<Dictionary<string, object>>("FileMeta")
|
||||||
.HasColumnType("jsonb")
|
.HasColumnType("jsonb")
|
||||||
.HasColumnName("file_meta");
|
.HasColumnName("file_meta");
|
||||||
@ -955,6 +1228,30 @@ namespace DysonNetwork.Sphere.Migrations
|
|||||||
b.Navigation("Account");
|
b.Navigation("Account");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DysonNetwork.Sphere.Account.Notification", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("DysonNetwork.Sphere.Account.Account", "Account")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("AccountId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired()
|
||||||
|
.HasConstraintName("fk_notifications_accounts_account_id");
|
||||||
|
|
||||||
|
b.Navigation("Account");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DysonNetwork.Sphere.Account.NotificationPushSubscription", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("DysonNetwork.Sphere.Account.Account", "Account")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("AccountId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired()
|
||||||
|
.HasConstraintName("fk_notification_push_subscriptions_accounts_account_id");
|
||||||
|
|
||||||
|
b.Navigation("Account");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("DysonNetwork.Sphere.Account.Profile", b =>
|
modelBuilder.Entity("DysonNetwork.Sphere.Account.Profile", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "Background")
|
b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "Background")
|
||||||
@ -1035,6 +1332,42 @@ namespace DysonNetwork.Sphere.Migrations
|
|||||||
b.Navigation("Challenge");
|
b.Navigation("Challenge");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroupMember", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("DysonNetwork.Sphere.Account.Account", "Account")
|
||||||
|
.WithMany("GroupMemberships")
|
||||||
|
.HasForeignKey("AccountId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired()
|
||||||
|
.HasConstraintName("fk_permission_group_member_accounts_account_id");
|
||||||
|
|
||||||
|
b.HasOne("DysonNetwork.Sphere.Permission.PermissionGroup", "Group")
|
||||||
|
.WithMany("Members")
|
||||||
|
.HasForeignKey("GroupId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired()
|
||||||
|
.HasConstraintName("fk_permission_group_member_permission_groups_group_id");
|
||||||
|
|
||||||
|
b.Navigation("Account");
|
||||||
|
|
||||||
|
b.Navigation("Group");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionNode", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("DysonNetwork.Sphere.Permission.PermissionNode", "Group")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("GroupId")
|
||||||
|
.HasConstraintName("fk_permission_nodes_permission_nodes_group_id");
|
||||||
|
|
||||||
|
b.HasOne("DysonNetwork.Sphere.Permission.PermissionGroup", null)
|
||||||
|
.WithMany("Nodes")
|
||||||
|
.HasForeignKey("PermissionGroupId")
|
||||||
|
.HasConstraintName("fk_permission_nodes_permission_groups_permission_group_id");
|
||||||
|
|
||||||
|
b.Navigation("Group");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("DysonNetwork.Sphere.Post.Post", b =>
|
modelBuilder.Entity("DysonNetwork.Sphere.Post.Post", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("DysonNetwork.Sphere.Post.Post", "ForwardedPost")
|
b.HasOne("DysonNetwork.Sphere.Post.Post", "ForwardedPost")
|
||||||
@ -1224,6 +1557,8 @@ namespace DysonNetwork.Sphere.Migrations
|
|||||||
|
|
||||||
b.Navigation("Contacts");
|
b.Navigation("Contacts");
|
||||||
|
|
||||||
|
b.Navigation("GroupMemberships");
|
||||||
|
|
||||||
b.Navigation("IncomingRelationships");
|
b.Navigation("IncomingRelationships");
|
||||||
|
|
||||||
b.Navigation("OutgoingRelationships");
|
b.Navigation("OutgoingRelationships");
|
||||||
@ -1234,6 +1569,13 @@ namespace DysonNetwork.Sphere.Migrations
|
|||||||
b.Navigation("Sessions");
|
b.Navigation("Sessions");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroup", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Members");
|
||||||
|
|
||||||
|
b.Navigation("Nodes");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("DysonNetwork.Sphere.Post.Post", b =>
|
modelBuilder.Entity("DysonNetwork.Sphere.Post.Post", b =>
|
||||||
{
|
{
|
||||||
b.Navigation("Attachments");
|
b.Navigation("Attachments");
|
||||||
|
53
DysonNetwork.Sphere/Permission/Permission.cs
Normal file
53
DysonNetwork.Sphere/Permission/Permission.cs
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using NodaTime;
|
||||||
|
|
||||||
|
namespace DysonNetwork.Sphere.Permission;
|
||||||
|
|
||||||
|
/// The permission node model provides the infrastructure of permission control in Dyson Network.
|
||||||
|
/// It based on the ABAC permission model.
|
||||||
|
///
|
||||||
|
/// The value can be any type, boolean and number for most cases and stored in jsonb.
|
||||||
|
///
|
||||||
|
/// The area represents the region this permission affects. For example, the pub:<publisherId>
|
||||||
|
/// indicates it's a permission node for the publishers managing.
|
||||||
|
///
|
||||||
|
/// And the actor shows who owns the permission, in most cases, the user:<userId>
|
||||||
|
/// and when the permission node has a GroupId, the actor will be set to the group, but it won't work on checking
|
||||||
|
/// expect the member of that permission group inherent the permission from the group.
|
||||||
|
[Index(nameof(Key), nameof(Area), nameof(Actor))]
|
||||||
|
public class PermissionNode : ModelBase
|
||||||
|
{
|
||||||
|
public Guid Id { get; set; } = Guid.NewGuid();
|
||||||
|
[MaxLength(1024)] public string Actor { get; set; } = null!;
|
||||||
|
[MaxLength(1024)] public string Area { get; set; } = null!;
|
||||||
|
[MaxLength(1024)] public string Key { get; set; } = null!;
|
||||||
|
[Column(TypeName = "jsonb")] public object Value { get; set; } = null!;
|
||||||
|
public Instant? ExpiredAt { get; set; } = null;
|
||||||
|
public Instant? AffectedAt { get; set; } = null;
|
||||||
|
|
||||||
|
public Guid? GroupId { get; set; } = null;
|
||||||
|
[JsonIgnore] public PermissionNode? Group { get; set; } = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PermissionGroup : ModelBase
|
||||||
|
{
|
||||||
|
public Guid Id { get; set; } = Guid.NewGuid();
|
||||||
|
[MaxLength(1024)] public string Key { get; set; } = null!;
|
||||||
|
|
||||||
|
public ICollection<PermissionNode> Nodes { get; set; } = new List<PermissionNode>();
|
||||||
|
[JsonIgnore] public ICollection<PermissionGroupMember> Members { get; set; } = new List<PermissionGroupMember>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PermissionGroupMember : ModelBase
|
||||||
|
{
|
||||||
|
public Guid GroupId { get; set; }
|
||||||
|
public long AccountId { get; set; }
|
||||||
|
public PermissionGroup Group { get; set; } = null!;
|
||||||
|
public Account.Account Account { get; set; } = null!;
|
||||||
|
|
||||||
|
public Instant? ExpiredAt { get; set; } = null;
|
||||||
|
public Instant? AffectedAt { get; set; } = null;
|
||||||
|
}
|
@ -17,6 +17,7 @@ public class PostController(AppDatabase db, PostService ps, IEnforcer enforcer)
|
|||||||
var currentUser = currentUserValue as Account.Account;
|
var currentUser = currentUserValue as Account.Account;
|
||||||
|
|
||||||
var totalCount = await db.Posts
|
var totalCount = await db.Posts
|
||||||
|
.FilterWithVisibility(currentUser, isListing: true)
|
||||||
.CountAsync();
|
.CountAsync();
|
||||||
var posts = await db.Posts
|
var posts = await db.Posts
|
||||||
.Include(e => e.Publisher)
|
.Include(e => e.Publisher)
|
||||||
@ -27,6 +28,7 @@ public class PostController(AppDatabase db, PostService ps, IEnforcer enforcer)
|
|||||||
.Include(e => e.Attachments)
|
.Include(e => e.Attachments)
|
||||||
.Include(e => e.Categories)
|
.Include(e => e.Categories)
|
||||||
.Include(e => e.Tags)
|
.Include(e => e.Tags)
|
||||||
|
.Where(e => e.RepliedPostId == null)
|
||||||
.FilterWithVisibility(currentUser, isListing: true)
|
.FilterWithVisibility(currentUser, isListing: true)
|
||||||
.OrderByDescending(e => e.PublishedAt ?? e.CreatedAt)
|
.OrderByDescending(e => e.PublishedAt ?? e.CreatedAt)
|
||||||
.Skip(offset)
|
.Skip(offset)
|
||||||
@ -76,6 +78,7 @@ public class PostController(AppDatabase db, PostService ps, IEnforcer enforcer)
|
|||||||
|
|
||||||
var totalCount = await db.Posts
|
var totalCount = await db.Posts
|
||||||
.Where(e => e.RepliedPostId == post.Id)
|
.Where(e => e.RepliedPostId == post.Id)
|
||||||
|
.FilterWithVisibility(currentUser, isListing: true)
|
||||||
.CountAsync();
|
.CountAsync();
|
||||||
var posts = await db.Posts
|
var posts = await db.Posts
|
||||||
.Where(e => e.RepliedPostId == id)
|
.Where(e => e.RepliedPostId == id)
|
||||||
@ -110,6 +113,8 @@ public class PostController(AppDatabase db, PostService ps, IEnforcer enforcer)
|
|||||||
[MaxLength(32)] public List<string>? Attachments { get; set; }
|
[MaxLength(32)] public List<string>? Attachments { get; set; }
|
||||||
public Dictionary<string, object>? Meta { get; set; }
|
public Dictionary<string, object>? Meta { get; set; }
|
||||||
public Instant? PublishedAt { get; set; }
|
public Instant? PublishedAt { get; set; }
|
||||||
|
public long? RepliedPostId { get; set; }
|
||||||
|
public long? ForwardedPostId { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
@ -155,6 +160,22 @@ public class PostController(AppDatabase db, PostService ps, IEnforcer enforcer)
|
|||||||
Publisher = publisher,
|
Publisher = publisher,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (request.RepliedPostId is not null)
|
||||||
|
{
|
||||||
|
var repliedPost = await db.Posts.FindAsync(request.RepliedPostId.Value);
|
||||||
|
if (repliedPost is null) return BadRequest("Post replying to was not found.");
|
||||||
|
post.RepliedPost = repliedPost;
|
||||||
|
post.RepliedPostId = repliedPost.Id;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.ForwardedPostId is not null)
|
||||||
|
{
|
||||||
|
var forwardedPost = await db.Posts.FindAsync(request.ForwardedPostId.Value);
|
||||||
|
if (forwardedPost is null) return BadRequest("Forwarded post was not found.");
|
||||||
|
post.ForwardedPost = forwardedPost;
|
||||||
|
post.ForwardedPostId = forwardedPost.Id;
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
post = await ps.PostAsync(
|
post = await ps.PostAsync(
|
||||||
@ -226,6 +247,7 @@ public class PostController(AppDatabase db, PostService ps, IEnforcer enforcer)
|
|||||||
|
|
||||||
var post = await db.Posts
|
var post = await db.Posts
|
||||||
.Where(e => e.Id == id)
|
.Where(e => e.Id == id)
|
||||||
|
.Include(e => e.Publisher)
|
||||||
.Include(e => e.Attachments)
|
.Include(e => e.Attachments)
|
||||||
.FirstOrDefaultAsync();
|
.FirstOrDefaultAsync();
|
||||||
if (post is null) return NotFound();
|
if (post is null) return NotFound();
|
||||||
|
@ -130,6 +130,7 @@ builder.Services.AddSwaggerGen(options =>
|
|||||||
builder.Services.AddOpenApi();
|
builder.Services.AddOpenApi();
|
||||||
|
|
||||||
builder.Services.AddScoped<AccountService>();
|
builder.Services.AddScoped<AccountService>();
|
||||||
|
builder.Services.AddScoped<NotificationService>();
|
||||||
builder.Services.AddScoped<AuthService>();
|
builder.Services.AddScoped<AuthService>();
|
||||||
builder.Services.AddScoped<FileService>();
|
builder.Services.AddScoped<FileService>();
|
||||||
builder.Services.AddScoped<PublisherService>();
|
builder.Services.AddScoped<PublisherService>();
|
||||||
|
@ -5,7 +5,7 @@ using NodaTime;
|
|||||||
|
|
||||||
namespace DysonNetwork.Sphere.Storage;
|
namespace DysonNetwork.Sphere.Storage;
|
||||||
|
|
||||||
public class RemoteStorageConfig
|
public abstract class RemoteStorageConfig
|
||||||
{
|
{
|
||||||
public string Id { get; set; } = string.Empty;
|
public string Id { get; set; } = string.Empty;
|
||||||
public string Label { get; set; } = string.Empty;
|
public string Label { get; set; } = string.Empty;
|
||||||
@ -22,7 +22,7 @@ public class RemoteStorageConfig
|
|||||||
|
|
||||||
public class CloudFile : ModelBase
|
public class CloudFile : ModelBase
|
||||||
{
|
{
|
||||||
public string Id { get; set; } = Guid.NewGuid().ToString();
|
[MaxLength(128)] public string Id { get; set; } = Guid.NewGuid().ToString();
|
||||||
[MaxLength(1024)] public string Name { get; set; } = string.Empty;
|
[MaxLength(1024)] public string Name { get; set; } = string.Empty;
|
||||||
[MaxLength(4096)] public string? Description { get; set; }
|
[MaxLength(4096)] public string? Description { get; set; }
|
||||||
[Column(TypeName = "jsonb")] public Dictionary<string, object>? FileMeta { get; set; } = null!;
|
[Column(TypeName = "jsonb")] public Dictionary<string, object>? FileMeta { get; set; } = null!;
|
||||||
@ -32,6 +32,7 @@ public class CloudFile : ModelBase
|
|||||||
[MaxLength(256)] public string? Hash { get; set; }
|
[MaxLength(256)] public string? Hash { get; set; }
|
||||||
public long Size { get; set; }
|
public long Size { get; set; }
|
||||||
public Instant? UploadedAt { get; set; }
|
public Instant? UploadedAt { get; set; }
|
||||||
|
public Instant? ExpiredAt { get; set; }
|
||||||
[MaxLength(128)] public string? UploadedTo { get; set; }
|
[MaxLength(128)] public string? UploadedTo { get; set; }
|
||||||
|
|
||||||
// Metrics
|
// Metrics
|
||||||
|
@ -249,9 +249,12 @@ public class CloudFileUnusedRecyclingJob(AppDatabase db, FileService fs, ILogger
|
|||||||
logger.LogInformation("Deleting unused cloud files...");
|
logger.LogInformation("Deleting unused cloud files...");
|
||||||
|
|
||||||
var cutoff = SystemClock.Instance.GetCurrentInstant() - Duration.FromHours(1);
|
var cutoff = SystemClock.Instance.GetCurrentInstant() - Duration.FromHours(1);
|
||||||
|
var now = SystemClock.Instance.GetCurrentInstant();
|
||||||
var files = db.Files
|
var files = db.Files
|
||||||
.Where(f => f.UsedCount == 0)
|
.Where(f =>
|
||||||
.Where(f => f.CreatedAt < cutoff)
|
(f.ExpiredAt == null && f.UsedCount == 0 && f.CreatedAt < cutoff) ||
|
||||||
|
(f.ExpiredAt != null && f.ExpiredAt >= now)
|
||||||
|
)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
logger.LogInformation($"Deleting {files.Count} unused cloud files...");
|
logger.LogInformation($"Deleting {files.Count} unused cloud files...");
|
||||||
|
@ -49,5 +49,17 @@
|
|||||||
"Provider": "recaptcha",
|
"Provider": "recaptcha",
|
||||||
"ApiKey": "6LfIzSArAAAAAN413MtycDcPlKa636knBSAhbzj-",
|
"ApiKey": "6LfIzSArAAAAAN413MtycDcPlKa636knBSAhbzj-",
|
||||||
"ApiSecret": ""
|
"ApiSecret": ""
|
||||||
|
},
|
||||||
|
"Notifications": {
|
||||||
|
"Push": {
|
||||||
|
"Production": true,
|
||||||
|
"Google": "path/to/config.json",
|
||||||
|
"Apple": {
|
||||||
|
"PrivateKey": "path/to/cert.p8",
|
||||||
|
"PrivateKeyId": "",
|
||||||
|
"TeamId": "",
|
||||||
|
"BundleIdentifier": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||||
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AApnSender_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F6aadc2cf048f477d8636fb2def7b73648200_003Fc5_003F2a1973a9_003FApnSender_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AAuthenticationMiddleware_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fe49de78932194d52a02b07486c6d023a24600_003F2f_003F7ab1cc57_003FAuthenticationMiddleware_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AAuthenticationMiddleware_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fe49de78932194d52a02b07486c6d023a24600_003F2f_003F7ab1cc57_003FAuthenticationMiddleware_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AAuthorizationAppBuilderExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F2ff26593f91746d7a53418a46dc419d1f200_003F4b_003F56550da2_003FAuthorizationAppBuilderExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AAuthorizationAppBuilderExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F2ff26593f91746d7a53418a46dc419d1f200_003F4b_003F56550da2_003FAuthorizationAppBuilderExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ABucketArgs_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003Fd515fb889657fcdcace3fed90735057b458ff9e0bb60bded7c8fe8b3a4673c_003FBucketArgs_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ABucketArgs_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003Fd515fb889657fcdcace3fed90735057b458ff9e0bb60bded7c8fe8b3a4673c_003FBucketArgs_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
@ -22,6 +23,7 @@
|
|||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AFileResult_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F0b5acdd962e549369896cece0026e556214600_003F8c_003F9f6e3f4f_003FFileResult_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AFileResult_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F0b5acdd962e549369896cece0026e556214600_003F8c_003F9f6e3f4f_003FFileResult_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AForwardedHeaders_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fcfe5737f9bb84738979cbfedd11822a8ea00_003F50_003F9a335f87_003FForwardedHeaders_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AForwardedHeaders_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fcfe5737f9bb84738979cbfedd11822a8ea00_003F50_003F9a335f87_003FForwardedHeaders_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AImageFile_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fa932cb9090ed48088111ae919dcdd9021ba00_003F71_003F0a804432_003FImageFile_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AImageFile_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fa932cb9090ed48088111ae919dcdd9021ba00_003F71_003F0a804432_003FImageFile_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIndexAttribute_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fe38f14ac86274ebb9b366729231d1c1a8838_003F8b_003F2890293d_003FIndexAttribute_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIntentType_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F8bb08a178b5b43c5bac20a5a54159a5b2a800_003Fbf_003Ffcb84131_003FIntentType_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIntentType_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F8bb08a178b5b43c5bac20a5a54159a5b2a800_003Fbf_003Ffcb84131_003FIntentType_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIServiceCollectionQuartzConfigurator_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F1edbd6e24d7b430fabce72177269baa19200_003F67_003Faee36f5b_003FIServiceCollectionQuartzConfigurator_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIServiceCollectionQuartzConfigurator_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F1edbd6e24d7b430fabce72177269baa19200_003F67_003Faee36f5b_003FIServiceCollectionQuartzConfigurator_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AITusStore_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F8bb08a178b5b43c5bac20a5a54159a5b2a800_003Fb1_003F7e861de5_003FITusStore_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AITusStore_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F8bb08a178b5b43c5bac20a5a54159a5b2a800_003Fb1_003F7e861de5_003FITusStore_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user