From ef9175d27d5020e67d6d546f0f54b0768d47e30f Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Mon, 14 Jul 2025 13:50:41 +0800 Subject: [PATCH] :sparkles: Remix file service --- DysonNetwork.Drive/Storage/CloudFile.cs | 4 +- .../Storage/FileReferenceServiceGrpc.cs | 55 ++++++--- DysonNetwork.Drive/Storage/FileServiceGrpc.cs | 114 +----------------- DysonNetwork.Pass/Account/Account.cs | 10 +- .../Account/AccountController.cs | 5 - .../Account/AccountCurrentController.cs | 41 ++++++- .../Account/AccountEventService.cs | 2 +- .../Data/CloudFileReferenceObject.cs | 22 +++- DysonNetwork.Shared/Data/ICloudFile.cs | 2 +- DysonNetwork.Shared/Proto/GrpcTypeHelper.cs | 2 +- DysonNetwork.Shared/Proto/account.proto | 4 - DysonNetwork.Shared/Proto/file.proto | 33 +---- 12 files changed, 110 insertions(+), 184 deletions(-) diff --git a/DysonNetwork.Drive/Storage/CloudFile.cs b/DysonNetwork.Drive/Storage/CloudFile.cs index 6911dc2..81d9239 100644 --- a/DysonNetwork.Drive/Storage/CloudFile.cs +++ b/DysonNetwork.Drive/Storage/CloudFile.cs @@ -29,7 +29,7 @@ public class CloudFileReferenceObject : ModelBase, ICloudFile { public string Id { get; set; } = null!; public string Name { get; set; } = string.Empty; - public Dictionary? FileMeta { get; set; } = null!; + public Dictionary FileMeta { get; set; } = null!; public Dictionary? UserMeta { get; set; } = null!; public string? MimeType { get; set; } public string? Hash { get; set; } @@ -45,7 +45,7 @@ public class CloudFile : ModelBase, ICloudFile, IIdentifiedResource [MaxLength(1024)] public string Name { get; set; } = string.Empty; [MaxLength(4096)] public string? Description { get; set; } - [Column(TypeName = "jsonb")] public Dictionary? FileMeta { get; set; } = null!; + [Column(TypeName = "jsonb")] public Dictionary FileMeta { get; set; } = null!; [Column(TypeName = "jsonb")] public Dictionary? UserMeta { get; set; } = null!; [Column(TypeName = "jsonb")] public List? SensitiveMarks { get; set; } = []; [MaxLength(256)] public string? MimeType { get; set; } diff --git a/DysonNetwork.Drive/Storage/FileReferenceServiceGrpc.cs b/DysonNetwork.Drive/Storage/FileReferenceServiceGrpc.cs index d31a8c3..2a1459e 100644 --- a/DysonNetwork.Drive/Storage/FileReferenceServiceGrpc.cs +++ b/DysonNetwork.Drive/Storage/FileReferenceServiceGrpc.cs @@ -5,15 +5,18 @@ using Duration = NodaTime.Duration; namespace DysonNetwork.Drive.Storage { - public class FileReferenceServiceGrpc(FileReferenceService fileReferenceService) : Shared.Proto.FileReferenceService.FileReferenceServiceBase + public class FileReferenceServiceGrpc(FileReferenceService fileReferenceService) + : Shared.Proto.FileReferenceService.FileReferenceServiceBase { - public override async Task CreateReference(CreateReferenceRequest request, ServerCallContext context) + public override async Task CreateReference(CreateReferenceRequest request, + ServerCallContext context) { Instant? expiredAt = null; if (request.ExpiredAt != null) expiredAt = Instant.FromUnixTimeSeconds(request.ExpiredAt.Seconds); else if (request.Duration != null) - expiredAt = SystemClock.Instance.GetCurrentInstant() + Duration.FromTimeSpan(request.Duration.ToTimeSpan()); + expiredAt = SystemClock.Instance.GetCurrentInstant() + + Duration.FromTimeSpan(request.Duration.ToTimeSpan()); var reference = await fileReferenceService.CreateReferenceAsync( request.FileId, @@ -24,7 +27,8 @@ namespace DysonNetwork.Drive.Storage return reference.ToProtoValue(); } - public override async Task GetReferences(GetReferencesRequest request, ServerCallContext context) + public override async Task GetReferences(GetReferencesRequest request, + ServerCallContext context) { var references = await fileReferenceService.GetReferencesAsync(request.FileId); var response = new GetReferencesResponse(); @@ -32,13 +36,15 @@ namespace DysonNetwork.Drive.Storage return response; } - public override async Task GetReferenceCount(GetReferenceCountRequest request, ServerCallContext context) + public override async Task GetReferenceCount(GetReferenceCountRequest request, + ServerCallContext context) { var count = await fileReferenceService.GetReferenceCountAsync(request.FileId); return new GetReferenceCountResponse { Count = count }; } - public override async Task GetResourceReferences(GetResourceReferencesRequest request, ServerCallContext context) + public override async Task GetResourceReferences(GetResourceReferencesRequest request, + ServerCallContext context) { var references = await fileReferenceService.GetResourceReferencesAsync(request.ResourceId, request.Usage); var response = new GetReferencesResponse(); @@ -46,7 +52,8 @@ namespace DysonNetwork.Drive.Storage return response; } - public override async Task GetResourceFiles(GetResourceFilesRequest request, ServerCallContext context) + public override async Task GetResourceFiles(GetResourceFilesRequest request, + ServerCallContext context) { var files = await fileReferenceService.GetResourceFilesAsync(request.ResourceId, request.Usage); var response = new GetResourceFilesResponse(); @@ -54,19 +61,27 @@ namespace DysonNetwork.Drive.Storage return response; } - public override async Task DeleteResourceReferences(DeleteResourceReferencesRequest request, ServerCallContext context) + public override async Task DeleteResourceReferences( + DeleteResourceReferencesRequest request, ServerCallContext context) { - var deletedCount = await fileReferenceService.DeleteResourceReferencesAsync(request.ResourceId, request.Usage); + var deletedCount = 0; + if (request.Usage is null) + deletedCount = await fileReferenceService.DeleteResourceReferencesAsync(request.ResourceId); + else + deletedCount = + await fileReferenceService.DeleteResourceReferencesAsync(request.ResourceId, request.Usage!); return new DeleteResourceReferencesResponse { DeletedCount = deletedCount }; } - public override async Task DeleteReference(DeleteReferenceRequest request, ServerCallContext context) + public override async Task DeleteReference(DeleteReferenceRequest request, + ServerCallContext context) { var success = await fileReferenceService.DeleteReferenceAsync(Guid.Parse(request.ReferenceId)); return new DeleteReferenceResponse { Success = success }; } - public override async Task UpdateResourceFiles(UpdateResourceFilesRequest request, ServerCallContext context) + public override async Task UpdateResourceFiles(UpdateResourceFilesRequest request, + ServerCallContext context) { Instant? expiredAt = null; if (request.ExpiredAt != null) @@ -75,7 +90,8 @@ namespace DysonNetwork.Drive.Storage } else if (request.Duration != null) { - expiredAt = SystemClock.Instance.GetCurrentInstant() + Duration.FromTimeSpan(request.Duration.ToTimeSpan()); + expiredAt = SystemClock.Instance.GetCurrentInstant() + + Duration.FromTimeSpan(request.Duration.ToTimeSpan()); } var references = await fileReferenceService.UpdateResourceFilesAsync( @@ -89,7 +105,8 @@ namespace DysonNetwork.Drive.Storage return response; } - public override async Task SetReferenceExpiration(SetReferenceExpirationRequest request, ServerCallContext context) + public override async Task SetReferenceExpiration( + SetReferenceExpirationRequest request, ServerCallContext context) { Instant? expiredAt = null; if (request.ExpiredAt != null) @@ -98,21 +115,25 @@ namespace DysonNetwork.Drive.Storage } else if (request.Duration != null) { - expiredAt = SystemClock.Instance.GetCurrentInstant() + Duration.FromTimeSpan(request.Duration.ToTimeSpan()); + expiredAt = SystemClock.Instance.GetCurrentInstant() + + Duration.FromTimeSpan(request.Duration.ToTimeSpan()); } - var success = await fileReferenceService.SetReferenceExpirationAsync(Guid.Parse(request.ReferenceId), expiredAt); + var success = + await fileReferenceService.SetReferenceExpirationAsync(Guid.Parse(request.ReferenceId), expiredAt); return new SetReferenceExpirationResponse { Success = success }; } - public override async Task SetFileReferencesExpiration(SetFileReferencesExpirationRequest request, ServerCallContext context) + public override async Task SetFileReferencesExpiration( + SetFileReferencesExpirationRequest request, ServerCallContext context) { var expiredAt = Instant.FromUnixTimeSeconds(request.ExpiredAt.Seconds); var updatedCount = await fileReferenceService.SetFileReferencesExpirationAsync(request.FileId, expiredAt); return new SetFileReferencesExpirationResponse { UpdatedCount = updatedCount }; } - public override async Task HasFileReferences(HasFileReferencesRequest request, ServerCallContext context) + public override async Task HasFileReferences(HasFileReferencesRequest request, + ServerCallContext context) { var hasReferences = await fileReferenceService.HasFileReferencesAsync(request.FileId); return new HasFileReferencesResponse { HasReferences = hasReferences }; diff --git a/DysonNetwork.Drive/Storage/FileServiceGrpc.cs b/DysonNetwork.Drive/Storage/FileServiceGrpc.cs index ef80f5c..4b62afb 100644 --- a/DysonNetwork.Drive/Storage/FileServiceGrpc.cs +++ b/DysonNetwork.Drive/Storage/FileServiceGrpc.cs @@ -12,7 +12,8 @@ namespace DysonNetwork.Drive.Storage return file?.ToProtoValue() ?? throw new RpcException(new Status(StatusCode.NotFound, "File not found")); } - public override async Task UpdateFile(UpdateFileRequest request, ServerCallContext context) + public override async Task UpdateFile(UpdateFileRequest request, + ServerCallContext context) { var file = await fileService.GetFileAsync(request.File.Id); if (file == null) @@ -33,113 +34,10 @@ namespace DysonNetwork.Drive.Storage return new Empty(); } - public override async Task ProcessNewFile(IAsyncStreamReader requestStream, - ServerCallContext context) - { - ProcessNewFileRequest? metadataRequest = null; - var chunks = new List(); - - await foreach (var message in requestStream.ReadAllAsync()) - { - if (message.DataCase == ProcessNewFileRequest.DataOneofCase.Metadata) - { - metadataRequest = message; - } - else if (message.DataCase == ProcessNewFileRequest.DataOneofCase.Chunk) - { - chunks.Add(message.Chunk.ToByteArray()); - } - } - - if (metadataRequest == null || metadataRequest.Metadata == null) - { - throw new RpcException(new Status(StatusCode.InvalidArgument, "Missing file metadata")); - } - - var metadata = metadataRequest.Metadata; - using var memoryStream = new MemoryStream(); - foreach (var chunk in chunks) - { - await memoryStream.WriteAsync(chunk); - } - - memoryStream.Position = 0; - - // Assuming you have an Account object available or can create a dummy one for now - // You might need to adjust this based on how accounts are handled in your system - var dummyAccount = new Account { Id = metadata.AccountId }; - - var cloudFile = await fileService.ProcessNewFileAsync( - dummyAccount, - metadata.FileId, - memoryStream, - metadata.FileName, - metadata.ContentType - ); - return cloudFile.ToProtoValue(); - } - - public override async Task UploadFileToRemote( - IAsyncStreamReader requestStream, ServerCallContext context) - { - UploadFileToRemoteRequest? metadataRequest = null; - var chunks = new List(); - - await foreach (var message in requestStream.ReadAllAsync()) - { - if (message.DataCase == UploadFileToRemoteRequest.DataOneofCase.Metadata) - { - metadataRequest = message; - } - else if (message.DataCase == UploadFileToRemoteRequest.DataOneofCase.Chunk) - { - chunks.Add(message.Chunk.ToByteArray()); - } - } - - if (metadataRequest == null || metadataRequest.Metadata == null) - { - throw new RpcException(new Status(StatusCode.InvalidArgument, "Missing upload metadata")); - } - - var metadata = metadataRequest.Metadata; - using var memoryStream = new MemoryStream(); - foreach (var chunk in chunks) - { - await memoryStream.WriteAsync(chunk); - } - - memoryStream.Position = 0; - - var file = await fileService.GetFileAsync(metadata.FileId); - if (file == null) - { - throw new RpcException(new Status(StatusCode.NotFound, "File not found")); - } - - var uploadedFile = await fileService.UploadFileToRemoteAsync( - file, - memoryStream, - metadata.TargetRemote, - metadata.Suffix - ); - return uploadedFile.ToProtoValue(); - } - - public override async Task DeleteFileData(DeleteFileDataRequest request, ServerCallContext context) - { - var file = await fileService.GetFileAsync(request.FileId); - if (file == null) - { - throw new RpcException(new Status(StatusCode.NotFound, "File not found")); - } - - await fileService.DeleteFileDataAsync(file); - return new Empty(); - } - - public override async Task LoadFromReference(LoadFromReferenceRequest request, - ServerCallContext context) + public override async Task LoadFromReference( + LoadFromReferenceRequest request, + ServerCallContext context + ) { // Assuming CloudFileReferenceObject is a simple class/struct that holds an ID // You might need to define this or adjust the LoadFromReference method in FileService diff --git a/DysonNetwork.Pass/Account/Account.cs b/DysonNetwork.Pass/Account/Account.cs index 3078c7c..7015c9a 100644 --- a/DysonNetwork.Pass/Account/Account.cs +++ b/DysonNetwork.Pass/Account/Account.cs @@ -78,7 +78,7 @@ public abstract class Leveling ]; } -public class AccountProfile : ModelBase +public class AccountProfile : ModelBase, IIdentifiedResource { public Guid Id { get; set; } [MaxLength(256)] public string? FirstName { get; set; } @@ -104,10 +104,6 @@ public class AccountProfile : ModelBase : (Experience - Leveling.ExperiencePerLevel[Level]) * 100.0 / (Leveling.ExperiencePerLevel[Level + 1] - Leveling.ExperiencePerLevel[Level]); - // Outdated fields, for backward compability - [MaxLength(32)] public string? PictureId { get; set; } - [MaxLength(32)] public string? BackgroundId { get; set; } - [Column(TypeName = "jsonb")] public CloudFileReferenceObject? Picture { get; set; } [Column(TypeName = "jsonb")] public CloudFileReferenceObject? Background { get; set; } @@ -132,8 +128,6 @@ public class AccountProfile : ModelBase Experience = Experience, Level = Level, LevelingProgress = LevelingProgress, - PictureId = PictureId ?? string.Empty, - BackgroundId = BackgroundId ?? string.Empty, Picture = Picture?.ToProtoValue(), Background = Background?.ToProtoValue(), AccountId = AccountId.ToString(), @@ -143,6 +137,8 @@ public class AccountProfile : ModelBase return proto; } + + public string ResourceIdentifier => $"account:profile:{Id}"; } public class AccountContact : ModelBase diff --git a/DysonNetwork.Pass/Account/AccountController.cs b/DysonNetwork.Pass/Account/AccountController.cs index 98a2564..ee2054b 100644 --- a/DysonNetwork.Pass/Account/AccountController.cs +++ b/DysonNetwork.Pass/Account/AccountController.cs @@ -1,13 +1,8 @@ using System.ComponentModel.DataAnnotations; using DysonNetwork.Pass.Auth; -using DysonNetwork.Pass; -using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using NodaTime; -using NodaTime.Extensions; -using System.Collections.Generic; -using DysonNetwork.Pass.Account; namespace DysonNetwork.Pass.Account; diff --git a/DysonNetwork.Pass/Account/AccountCurrentController.cs b/DysonNetwork.Pass/Account/AccountCurrentController.cs index f5e830a..ea5f57d 100644 --- a/DysonNetwork.Pass/Account/AccountCurrentController.cs +++ b/DysonNetwork.Pass/Account/AccountCurrentController.cs @@ -1,10 +1,14 @@ using System.ComponentModel.DataAnnotations; -using DysonNetwork.Pass.Auth; using DysonNetwork.Pass.Permission; +using DysonNetwork.Shared.Data; +using DysonNetwork.Shared.Proto; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using NodaTime; +using AuthService = DysonNetwork.Pass.Auth.AuthService; +using AuthSession = DysonNetwork.Pass.Auth.AuthSession; +using ChallengePlatform = DysonNetwork.Pass.Auth.ChallengePlatform; namespace DysonNetwork.Pass.Account; @@ -15,7 +19,9 @@ public class AccountCurrentController( AppDatabase db, AccountService accounts, AccountEventService events, - AuthService auth + AuthService auth, + FileService.FileServiceClient files, + FileReferenceService.FileReferenceServiceClient fileRefs ) : ControllerBase { [HttpGet] @@ -94,12 +100,37 @@ public class AccountCurrentController( if (request.PictureId is not null) { - // TODO: Create reference, set profile picture + var file = await files.GetFileAsync(new GetFileRequest { Id = request.PictureId }); + if (profile.Picture is not null) + await fileRefs.DeleteResourceReferencesAsync( + new DeleteResourceReferencesRequest { ResourceId = profile.ResourceIdentifier } + ); + await fileRefs.CreateReferenceAsync( + new CreateReferenceRequest + { + ResourceId = profile.ResourceIdentifier, + FileId = request.PictureId, + Usage = "profile.picture" + } + ); + profile.Picture = CloudFileReferenceObject.FromProtoValue(file); } - if (request.BackgroundId is not null) { - // TODO: Create reference, set profile background + var file = await files.GetFileAsync(new GetFileRequest { Id = request.BackgroundId }); + if (profile.Background is not null) + await fileRefs.DeleteResourceReferencesAsync( + new DeleteResourceReferencesRequest { ResourceId = profile.ResourceIdentifier } + ); + await fileRefs.CreateReferenceAsync( + new CreateReferenceRequest + { + ResourceId = profile.ResourceIdentifier, + FileId = request.BackgroundId, + Usage = "profile.background" + } + ); + profile.Background = CloudFileReferenceObject.FromProtoValue(file); } db.Update(profile); diff --git a/DysonNetwork.Pass/Account/AccountEventService.cs b/DysonNetwork.Pass/Account/AccountEventService.cs index 5fe756b..8040e7a 100644 --- a/DysonNetwork.Pass/Account/AccountEventService.cs +++ b/DysonNetwork.Pass/Account/AccountEventService.cs @@ -100,7 +100,7 @@ public class AccountEventService( } } - if (cacheMissUserIds.Any()) + if (cacheMissUserIds.Count != 0) { var now = SystemClock.Instance.GetCurrentInstant(); var statusesFromDb = await db.AccountStatuses diff --git a/DysonNetwork.Shared/Data/CloudFileReferenceObject.cs b/DysonNetwork.Shared/Data/CloudFileReferenceObject.cs index b20826f..4866115 100644 --- a/DysonNetwork.Shared/Data/CloudFileReferenceObject.cs +++ b/DysonNetwork.Shared/Data/CloudFileReferenceObject.cs @@ -1,3 +1,4 @@ +using DysonNetwork.Shared.Proto; using Google.Protobuf.WellKnownTypes; namespace DysonNetwork.Shared.Data; @@ -10,13 +11,30 @@ public class CloudFileReferenceObject : ModelBase, ICloudFile { public string Id { get; set; } = null!; public string Name { get; set; } = string.Empty; - public Dictionary? FileMeta { get; set; } = null!; - public Dictionary? UserMeta { get; set; } = null!; + public Dictionary FileMeta { get; set; } = null!; + public Dictionary UserMeta { get; set; } = null!; public string? MimeType { get; set; } public string? Hash { get; set; } public long Size { get; set; } public bool HasCompression { get; set; } = false; + public static CloudFileReferenceObject FromProtoValue(Proto.CloudFile proto) + { + return new CloudFileReferenceObject + { + Id = proto.Id, + Name = proto.Name, + FileMeta = proto.FileMeta + .ToDictionary(kvp => kvp.Key, kvp => GrpcTypeHelper.ConvertField(kvp.Value)), + UserMeta = proto.UserMeta + .ToDictionary(kvp => kvp.Key, kvp => GrpcTypeHelper.ConvertField(kvp.Value)), + MimeType = proto.MimeType, + Hash = proto.Hash, + Size = proto.Size, + HasCompression = proto.HasCompression + }; + } + /// /// Converts the current object to its protobuf representation /// diff --git a/DysonNetwork.Shared/Data/ICloudFile.cs b/DysonNetwork.Shared/Data/ICloudFile.cs index 35543a3..68c25f3 100644 --- a/DysonNetwork.Shared/Data/ICloudFile.cs +++ b/DysonNetwork.Shared/Data/ICloudFile.cs @@ -26,7 +26,7 @@ public interface ICloudFile /// /// Gets the file metadata dictionary. /// - Dictionary? FileMeta { get; } + Dictionary FileMeta { get; } /// /// Gets the user metadata dictionary. diff --git a/DysonNetwork.Shared/Proto/GrpcTypeHelper.cs b/DysonNetwork.Shared/Proto/GrpcTypeHelper.cs index 1d2d7bd..d81d092 100644 --- a/DysonNetwork.Shared/Proto/GrpcTypeHelper.cs +++ b/DysonNetwork.Shared/Proto/GrpcTypeHelper.cs @@ -75,7 +75,7 @@ public abstract class GrpcTypeHelper return result; } - private static object? ConvertField(Value value) + public static object? ConvertField(Value value) { return value.KindCase switch { diff --git a/DysonNetwork.Shared/Proto/account.proto b/DysonNetwork.Shared/Proto/account.proto index 59afef5..dd332e6 100644 --- a/DysonNetwork.Shared/Proto/account.proto +++ b/DysonNetwork.Shared/Proto/account.proto @@ -51,10 +51,6 @@ message AccountProfile { int32 level = 15; double leveling_progress = 16; - // Legacy fields - google.protobuf.StringValue picture_id = 17; - google.protobuf.StringValue background_id = 18; - CloudFile picture = 19; CloudFile background = 20; diff --git a/DysonNetwork.Shared/Proto/file.proto b/DysonNetwork.Shared/Proto/file.proto index 515e16f..8ba4d8e 100644 --- a/DysonNetwork.Shared/Proto/file.proto +++ b/DysonNetwork.Shared/Proto/file.proto @@ -58,15 +58,6 @@ service FileService { // Delete a file reference rpc DeleteFile(DeleteFileRequest) returns (google.protobuf.Empty); - // Process and upload a new file - rpc ProcessNewFile(stream ProcessNewFileRequest) returns (CloudFile); - - // Upload a file to remote storage - rpc UploadFileToRemote(stream UploadFileToRemoteRequest) returns (CloudFile); - - // Delete file data from storage - rpc DeleteFileData(DeleteFileDataRequest) returns (google.protobuf.Empty); - // Load files from references rpc LoadFromReference(LoadFromReferenceRequest) returns (LoadFromReferenceResponse); @@ -88,14 +79,6 @@ message UpdateFileRequest { google.protobuf.FieldMask update_mask = 2; } -// Request message for DeleteFile -message ProcessNewFileRequest { - oneof data { - FileMetadata metadata = 1; - bytes chunk = 2; - } -} - message FileMetadata { string file_id = 1; string file_name = 2; @@ -103,13 +86,6 @@ message FileMetadata { string account_id = 4; } -message UploadFileToRemoteRequest { - oneof data { - UploadMetadata metadata = 1; - bytes chunk = 2; - } -} - message UploadMetadata { string file_id = 1; string target_remote = 2; @@ -122,11 +98,6 @@ message DeleteFileRequest { bool purge = 2; } -message DeleteFileDataRequest { - string file_id = 1; - bool force = 2; -} - message LoadFromReferenceRequest { repeated string reference_ids = 1; } @@ -201,7 +172,7 @@ message GetResourceReferencesRequest { message GetResourceFilesRequest { string resource_id = 1; - string usage = 2; // Optional + optional string usage = 2; } message GetResourceFilesResponse { @@ -210,7 +181,7 @@ message GetResourceFilesResponse { message DeleteResourceReferencesRequest { string resource_id = 1; - string usage = 2; // Optional + optional string usage = 2; } message DeleteResourceReferencesResponse {