♻️ Centralized data models (wip)

This commit is contained in:
2025-09-27 14:09:28 +08:00
parent 51b6f7309e
commit e70d8371f8
206 changed files with 1352 additions and 2128 deletions

View File

@@ -1,4 +1,5 @@
using System.ComponentModel.DataAnnotations;
using DysonNetwork.Shared.Models;
using DysonNetwork.Shared.Proto;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
@@ -22,7 +23,7 @@ public class BundleController(AppDatabase db) : ControllerBase
}
[HttpGet("{id:guid}")]
public async Task<ActionResult<FileBundle>> GetBundle([FromRoute] Guid id, [FromQuery] string? passcode)
public async Task<ActionResult<SnFileBundle>> GetBundle([FromRoute] Guid id, [FromQuery] string? passcode)
{
var bundle = await db.Bundles
.Where(e => e.Id == id)
@@ -36,7 +37,7 @@ public class BundleController(AppDatabase db) : ControllerBase
[HttpGet("me")]
[Authorize]
public async Task<ActionResult<List<FileBundle>>> ListBundles(
public async Task<ActionResult<List<SnFileBundle>>> ListBundles(
[FromQuery] string? term,
[FromQuery] int offset = 0,
[FromQuery] int take = 20
@@ -65,7 +66,7 @@ public class BundleController(AppDatabase db) : ControllerBase
[HttpPost]
[Authorize]
public async Task<ActionResult<FileBundle>> CreateBundle([FromBody] BundleRequest request)
public async Task<ActionResult<SnFileBundle>> CreateBundle([FromBody] BundleRequest request)
{
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized();
var accountId = Guid.Parse(currentUser.Id);
@@ -77,7 +78,7 @@ public class BundleController(AppDatabase db) : ControllerBase
if (string.IsNullOrEmpty(request.Name))
request.Name = "Unnamed Bundle";
var bundle = new FileBundle
var bundle = new SnFileBundle
{
Slug = request.Slug,
Name = request.Name,
@@ -95,7 +96,7 @@ public class BundleController(AppDatabase db) : ControllerBase
[HttpPut("{id:guid}")]
[Authorize]
public async Task<ActionResult<FileBundle>> UpdateBundle([FromRoute] Guid id, [FromBody] BundleRequest request)
public async Task<ActionResult<SnFileBundle>> UpdateBundle([FromRoute] Guid id, [FromBody] BundleRequest request)
{
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized();
var accountId = Guid.Parse(currentUser.Id);

View File

@@ -1,142 +0,0 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Text.Json.Serialization;
using DysonNetwork.Shared.Data;
using DysonNetwork.Shared.Proto;
using NodaTime;
using NodaTime.Serialization.Protobuf;
namespace DysonNetwork.Drive.Storage;
public class CloudFile : ModelBase, ICloudFile, IIdentifiedResource
{
/// The id generated by TuS, basically just UUID remove the dash lines
[MaxLength(32)]
public string Id { get; set; } = Guid.NewGuid().ToString().Replace("-", string.Empty);
[MaxLength(1024)] public string Name { get; set; } = string.Empty;
[MaxLength(4096)] public string? Description { get; set; }
[Column(TypeName = "jsonb")] public Dictionary<string, object?>? FileMeta { get; set; }
[Column(TypeName = "jsonb")] public Dictionary<string, object?>? UserMeta { get; set; }
[Column(TypeName = "jsonb")] public List<ContentSensitiveMark>? SensitiveMarks { get; set; } = [];
[MaxLength(256)] public string? MimeType { get; set; }
[MaxLength(256)] public string? Hash { get; set; }
public Instant? ExpiredAt { get; set; }
public long Size { get; set; }
public Instant? UploadedAt { get; set; }
public bool HasCompression { get; set; } = false;
public bool HasThumbnail { get; set; } = false;
public bool IsEncrypted { get; set; } = false;
public FilePool? Pool { get; set; }
public Guid? PoolId { get; set; }
[JsonIgnore] public FileBundle? Bundle { get; set; }
public Guid? BundleId { get; set; }
/// <summary>
/// The field is set to true if the recycling job plans to delete the file.
/// Due to the unstable of the recycling job, this doesn't really delete the file until a human verifies it.
/// </summary>
public bool IsMarkedRecycle { get; set; } = false;
/// The object name which stored remotely,
/// multiple cloud file may have same storage id to indicate they are the same file
///
/// If the storage id was null and the uploaded at is not null, means it is an embedding file,
/// The embedding file means the file is store on another site,
/// or it is a webpage (based on mimetype)
[MaxLength(32)]
public string? StorageId { get; set; }
/// This field should be null when the storage id is filled
/// Indicates the off-site accessible url of the file
[MaxLength(4096)]
public string? StorageUrl { get; set; }
[NotMapped]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? FastUploadLink { get; set; }
public ICollection<CloudFileReference> References { get; set; } = new List<CloudFileReference>();
public Guid AccountId { get; set; }
public CloudFileReferenceObject ToReferenceObject()
{
return new CloudFileReferenceObject
{
CreatedAt = CreatedAt,
UpdatedAt = UpdatedAt,
DeletedAt = DeletedAt,
Id = Id,
Name = Name,
FileMeta = FileMeta ?? [],
UserMeta = UserMeta ?? [],
SensitiveMarks = SensitiveMarks,
MimeType = MimeType,
Hash = Hash,
Size = Size,
HasCompression = HasCompression
};
}
public string ResourceIdentifier => $"file:{Id}";
/// <summary>
/// Converts the CloudFile to a protobuf message
/// </summary>
/// <returns>The protobuf message representation of this object</returns>
public Shared.Proto.CloudFile ToProtoValue()
{
var proto = new Shared.Proto.CloudFile
{
Id = Id,
Name = Name,
MimeType = MimeType ?? string.Empty,
Hash = Hash ?? string.Empty,
Size = Size,
HasCompression = HasCompression,
Url = StorageUrl ?? string.Empty,
ContentType = MimeType ?? string.Empty,
UploadedAt = UploadedAt?.ToTimestamp(),
// Convert file metadata
FileMeta = GrpcTypeHelper.ConvertObjectToByteString(FileMeta),
// Convert user metadata
UserMeta = GrpcTypeHelper.ConvertObjectToByteString(UserMeta),
SensitiveMarks = GrpcTypeHelper.ConvertObjectToByteString(SensitiveMarks)
};
return proto;
}
}
public class CloudFileReference : ModelBase
{
public Guid Id { get; set; } = Guid.NewGuid();
[MaxLength(32)] public string FileId { get; set; } = null!;
public CloudFile File { get; set; } = null!;
[MaxLength(1024)] public string Usage { get; set; } = null!;
[MaxLength(1024)] public string ResourceId { get; set; } = null!;
/// <summary>
/// Optional expiration date for the file reference
/// </summary>
public Instant? ExpiredAt { get; set; }
/// <summary>
/// Converts the CloudFileReference to a protobuf message
/// </summary>
/// <returns>The protobuf message representation of this object</returns>
public Shared.Proto.CloudFileReference ToProtoValue()
{
return new Shared.Proto.CloudFileReference
{
Id = Id.ToString(),
FileId = FileId,
File = File?.ToProtoValue(),
Usage = Usage,
ResourceId = ResourceId,
ExpiredAt = ExpiredAt?.ToTimestamp()
};
}
}

View File

@@ -1,36 +0,0 @@
using System.ComponentModel.DataAnnotations;
using DysonNetwork.Shared.Data;
using Microsoft.EntityFrameworkCore;
using NodaTime;
namespace DysonNetwork.Drive.Storage;
[Index(nameof(Slug), IsUnique = true)]
public class FileBundle : ModelBase
{
public Guid Id { get; set; } = Guid.NewGuid();
[MaxLength(1024)] public string Slug { get; set; } = null!;
[MaxLength(1024)] public string Name { get; set; } = null!;
[MaxLength(8192)] public string? Description { get; set; }
[MaxLength(256)] public string? Passcode { get; set; }
public List<CloudFile> Files { get; set; } = new();
public Instant? ExpiredAt { get; set; }
public Guid AccountId { get; set; }
public FileBundle HashPasscode()
{
if (string.IsNullOrEmpty(Passcode)) return this;
Passcode = BCrypt.Net.BCrypt.HashPassword(Passcode);
return this;
}
public bool VerifyPasscode(string? passcode)
{
if (string.IsNullOrEmpty(Passcode)) return true;
if (string.IsNullOrEmpty(passcode)) return false;
return BCrypt.Net.BCrypt.Verify(passcode, Passcode);
}
}

View File

@@ -1,6 +1,6 @@
using DysonNetwork.Drive.Billing;
using DysonNetwork.Shared.Auth;
using DysonNetwork.Shared.Data;
using DysonNetwork.Shared.Models;
using DysonNetwork.Shared.Proto;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

View File

@@ -1,56 +0,0 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Text.Json.Serialization;
using DysonNetwork.Shared.Data;
using NodaTime;
namespace DysonNetwork.Drive.Storage;
public class RemoteStorageConfig
{
public string Region { get; set; } = string.Empty;
public string Bucket { get; set; } = string.Empty;
public string Endpoint { get; set; } = string.Empty;
public string SecretId { get; set; } = string.Empty;
public string SecretKey { get; set; } = string.Empty;
public bool EnableSigned { get; set; }
public bool EnableSsl { get; set; }
public string? ImageProxy { get; set; }
public string? AccessProxy { get; set; }
public Duration? Expiration { get; set; }
}
public class BillingConfig
{
public double? CostMultiplier { get; set; } = 1.0;
}
public class PolicyConfig
{
public bool EnableFastUpload { get; set; } = false;
public bool EnableRecycle { get; set; } = false;
public bool PublicIndexable { get; set; } = false;
public bool PublicUsable { get; set; } = false;
public bool NoOptimization { get; set; } = false;
public bool NoMetadata { get; set; } = false;
public bool AllowEncryption { get; set; } = true;
public bool AllowAnonymous { get; set; } = true;
public List<string>? AcceptTypes { get; set; }
public long? MaxFileSize { get; set; }
public int RequirePrivilege { get; set; } = 0;
}
public class FilePool : ModelBase, IIdentifiedResource
{
public Guid Id { get; set; } = Guid.NewGuid();
[MaxLength(1024)] public string Name { get; set; } = string.Empty;
[MaxLength(8192)] public string Description { get; set; } = string.Empty;
[Column(TypeName = "jsonb")] public RemoteStorageConfig StorageConfig { get; set; } = new();
[Column(TypeName = "jsonb")] public BillingConfig BillingConfig { get; set; } = new();
[Column(TypeName = "jsonb")] public PolicyConfig PolicyConfig { get; set; } = new();
public bool IsHidden { get; set; } = false;
public Guid? AccountId { get; set; }
public string ResourceIdentifier => $"file-pool/{Id}";
}

View File

@@ -1,3 +1,4 @@
using DysonNetwork.Shared.Models;
using DysonNetwork.Shared.Proto;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

View File

@@ -1,4 +1,5 @@
using DysonNetwork.Shared.Cache;
using DysonNetwork.Shared.Models;
using EFCore.BulkExtensions;
using Microsoft.EntityFrameworkCore;
using NodaTime;
@@ -347,7 +348,7 @@ public class FileReferenceService(AppDatabase db, FileService fileService, ICach
/// <param name="resourceId">The ID of the resource</param>
/// <param name="usage">Optional filter by usage context</param>
/// <returns>A list of files referenced by the resource</returns>
public async Task<List<CloudFile>> GetResourceFilesAsync(string resourceId, string? usage = null)
public async Task<List<SnCloudFile>> GetResourceFilesAsync(string resourceId, string? usage = null)
{
var query = db.FileReferences.Where(r => r.ResourceId == resourceId);

View File

@@ -12,9 +12,9 @@ using NATS.Client.Core;
using NetVips;
using NodaTime;
using System.Linq.Expressions;
using DysonNetwork.Shared.Data;
using Microsoft.EntityFrameworkCore.Query;
using NATS.Net;
using DysonNetwork.Shared.Models;
namespace DysonNetwork.Drive.Storage;
@@ -512,7 +512,7 @@ public class FileService(
}
}
private async Task<FileBundle?> GetBundleAsync(Guid id, Guid accountId)
private async Task<SnFileBundle?> GetBundleAsync(Guid id, Guid accountId)
{
var bundle = await db.Bundles
.Where(e => e.Id == id)
@@ -569,7 +569,7 @@ public class FileService(
await Task.WhenAll(tasks);
}
public async Task<List<CloudFile?>> LoadFromReference(List<CloudFileReferenceObject> references)
public async Task<List<CloudFile?>> LoadFromReference(List<SnCloudFileReferenceObject> references)
{
var cachedFiles = new Dictionary<string, CloudFile>();
var uncachedIds = new List<string>();

View File

@@ -1,3 +1,4 @@
using DysonNetwork.Shared.Models;
using NodaTime;
namespace DysonNetwork.Drive.Storage.Model
@@ -18,7 +19,7 @@ namespace DysonNetwork.Drive.Storage.Model
public class CreateUploadTaskResponse
{
public bool FileExists { get; set; }
public CloudFile? File { get; set; }
public SnCloudFile? File { get; set; }
public string? TaskId { get; set; }
public long? ChunkSize { get; set; }
public int? ChunksCount { get; set; }