diff --git a/DysonNetwork.Develop/Identity/CustomAppService.cs b/DysonNetwork.Develop/Identity/CustomAppService.cs index fde3f80e..e332ed24 100644 --- a/DysonNetwork.Develop/Identity/CustomAppService.cs +++ b/DysonNetwork.Develop/Identity/CustomAppService.cs @@ -45,6 +45,9 @@ public class CustomAppService( if (picture is null) throw new InvalidOperationException("Invalid picture id, unable to find the file on cloud."); app.Picture = SnCloudFileReferenceObject.FromProtoValue(picture); + + if (request.Status == Shared.Models.CustomAppStatus.Production) + await files.SetFilePublicAsync(new SetFilePublicRequest { FileId = request.PictureId }); } if (request.BackgroundId is not null) { @@ -54,6 +57,9 @@ public class CustomAppService( if (background is null) throw new InvalidOperationException("Invalid picture id, unable to find the file on cloud."); app.Background = SnCloudFileReferenceObject.FromProtoValue(background); + + if (request.Status == Shared.Models.CustomAppStatus.Production) + await files.SetFilePublicAsync(new SetFilePublicRequest { FileId = request.BackgroundId }); } db.CustomApps.Add(app); @@ -164,6 +170,7 @@ public class CustomAppService( public async Task UpdateAppAsync(SnCustomApp app, CustomAppController.CustomAppRequest request) { + var oldStatus = app.Status; if (request.Slug is not null) app.Slug = request.Slug; if (request.Name is not null) @@ -188,6 +195,9 @@ public class CustomAppService( if (picture is null) throw new InvalidOperationException("Invalid picture id, unable to find the file on cloud."); app.Picture = SnCloudFileReferenceObject.FromProtoValue(picture); + + if (app.Status == Shared.Models.CustomAppStatus.Production) + await files.SetFilePublicAsync(new SetFilePublicRequest { FileId = request.PictureId }); } if (request.BackgroundId is not null) { @@ -197,11 +207,29 @@ public class CustomAppService( if (background is null) throw new InvalidOperationException("Invalid picture id, unable to find the file on cloud."); app.Background = SnCloudFileReferenceObject.FromProtoValue(background); + + if (app.Status == Shared.Models.CustomAppStatus.Production) + await files.SetFilePublicAsync(new SetFilePublicRequest { FileId = request.BackgroundId }); } db.Update(app); await db.SaveChangesAsync(); + if (oldStatus != Shared.Models.CustomAppStatus.Production && app.Status == Shared.Models.CustomAppStatus.Production) + { + if (app.Picture is not null) + await files.SetFilePublicAsync(new SetFilePublicRequest { FileId = app.Picture.Id }); + if (app.Background is not null) + await files.SetFilePublicAsync(new SetFilePublicRequest { FileId = app.Background.Id }); + } + else if (oldStatus == Shared.Models.CustomAppStatus.Production && app.Status != Shared.Models.CustomAppStatus.Production) + { + if (app.Picture is not null) + await files.UnsetFilePublicAsync(new UnsetFilePublicRequest { FileId = app.Picture.Id }); + if (app.Background is not null) + await files.UnsetFilePublicAsync(new UnsetFilePublicRequest { FileId = app.Background.Id }); + } + return app; } diff --git a/DysonNetwork.Drive/Storage/FileController.cs b/DysonNetwork.Drive/Storage/FileController.cs index 45d1bf33..256fef14 100644 --- a/DysonNetwork.Drive/Storage/FileController.cs +++ b/DysonNetwork.Drive/Storage/FileController.cs @@ -80,9 +80,6 @@ public class FileController( if (currentUser?.IsSuperuser == true) return true; - // TODO Remove this when the other serivce will mark permission correctly. - return true; - var permission = await db.FilePermissions .FirstOrDefaultAsync(p => p.FileId == file.Id); diff --git a/DysonNetwork.Drive/Storage/FileService.cs b/DysonNetwork.Drive/Storage/FileService.cs index 5caf28a0..292ff8b0 100644 --- a/DysonNetwork.Drive/Storage/FileService.cs +++ b/DysonNetwork.Drive/Storage/FileService.cs @@ -854,6 +854,45 @@ public class FileService( await db.SaveChangesAsync(); return count; } + + public async Task SetPublicAsync(string fileId) + { + var existingPermission = await db.FilePermissions + .FirstOrDefaultAsync(p => + p.FileId == fileId && + p.SubjectType == SnFilePermissionType.Anyone && + p.Permission == SnFilePermissionLevel.Read); + + if (existingPermission != null) + return; + + var permission = new SnFilePermission + { + Id = Guid.NewGuid(), + FileId = fileId, + SubjectType = SnFilePermissionType.Anyone, + SubjectId = string.Empty, + Permission = SnFilePermissionLevel.Read + }; + + db.FilePermissions.Add(permission); + await db.SaveChangesAsync(); + } + + public async Task UnsetPublicAsync(string fileId) + { + var permission = await db.FilePermissions + .FirstOrDefaultAsync(p => + p.FileId == fileId && + p.SubjectType == SnFilePermissionType.Anyone && + p.Permission == SnFilePermissionLevel.Read); + + if (permission == null) + return; + + db.FilePermissions.Remove(permission); + await db.SaveChangesAsync(); + } } file class UpdatableCloudFile(SnCloudFile file) diff --git a/DysonNetwork.Drive/Storage/FileServiceGrpc.cs b/DysonNetwork.Drive/Storage/FileServiceGrpc.cs index a503ce95..2a8c4c03 100644 --- a/DysonNetwork.Drive/Storage/FileServiceGrpc.cs +++ b/DysonNetwork.Drive/Storage/FileServiceGrpc.cs @@ -46,5 +46,17 @@ namespace DysonNetwork.Drive.Storage await fileService._PurgeCacheAsync(request.FileId); return new Empty(); } + + public override async Task SetFilePublic(SetFilePublicRequest request, ServerCallContext context) + { + await fileService.SetPublicAsync(request.FileId); + return new Empty(); + } + + public override async Task UnsetFilePublic(UnsetFilePublicRequest request, ServerCallContext context) + { + await fileService.UnsetPublicAsync(request.FileId); + return new Empty(); + } } } \ No newline at end of file diff --git a/DysonNetwork.Pass/Account/AccountCurrentController.cs b/DysonNetwork.Pass/Account/AccountCurrentController.cs index 15e0ec1b..17553cfb 100644 --- a/DysonNetwork.Pass/Account/AccountCurrentController.cs +++ b/DysonNetwork.Pass/Account/AccountCurrentController.cs @@ -122,12 +122,16 @@ public class AccountCurrentController( { var file = await files.GetFileAsync(new GetFileRequest { Id = request.PictureId }); profile.Picture = SnCloudFileReferenceObject.FromProtoValue(file); + + await files.SetFilePublicAsync(new SetFilePublicRequest { FileId = request.PictureId }); } if (request.BackgroundId is not null) { var file = await files.GetFileAsync(new GetFileRequest { Id = request.BackgroundId }); profile.Background = SnCloudFileReferenceObject.FromProtoValue(file); + + await files.SetFilePublicAsync(new SetFilePublicRequest { FileId = request.BackgroundId }); } db.Update(profile); diff --git a/DysonNetwork.Pass/Account/AccountService.cs b/DysonNetwork.Pass/Account/AccountService.cs index e9868bf3..495aa89d 100644 --- a/DysonNetwork.Pass/Account/AccountService.cs +++ b/DysonNetwork.Pass/Account/AccountService.cs @@ -229,12 +229,16 @@ public class AccountService( { var file = await files.GetFileAsync(new GetFileRequest { Id = pictureId }); account.Profile.Picture = SnCloudFileReferenceObject.FromProtoValue(file); + + await files.SetFilePublicAsync(new SetFilePublicRequest { FileId = pictureId }); } if (!string.IsNullOrEmpty(backgroundId)) { var file = await files.GetFileAsync(new GetFileRequest { Id = backgroundId }); account.Profile.Background = SnCloudFileReferenceObject.FromProtoValue(file); + + await files.SetFilePublicAsync(new SetFilePublicRequest { FileId = backgroundId }); } db.Accounts.Add(account); diff --git a/DysonNetwork.Pass/Account/BotAccountReceiverGrpc.cs b/DysonNetwork.Pass/Account/BotAccountReceiverGrpc.cs index cdf98afe..62ad7d37 100644 --- a/DysonNetwork.Pass/Account/BotAccountReceiverGrpc.cs +++ b/DysonNetwork.Pass/Account/BotAccountReceiverGrpc.cs @@ -53,12 +53,16 @@ public class BotAccountReceiverGrpc( { var file = await files.GetFileAsync(new GetFileRequest { Id = request.PictureId }); account.Profile.Picture = SnCloudFileReferenceObject.FromProtoValue(file); + + await files.SetFilePublicAsync(new SetFilePublicRequest { FileId = request.PictureId }); } if (request.BackgroundId is not null) { var file = await files.GetFileAsync(new GetFileRequest { Id = request.BackgroundId }); account.Profile.Background = SnCloudFileReferenceObject.FromProtoValue(file); + + await files.SetFilePublicAsync(new SetFilePublicRequest { FileId = request.BackgroundId }); } db.Accounts.Update(account); diff --git a/DysonNetwork.Pass/Realm/RealmController.cs b/DysonNetwork.Pass/Realm/RealmController.cs index 24fcf15d..15908395 100644 --- a/DysonNetwork.Pass/Realm/RealmController.cs +++ b/DysonNetwork.Pass/Realm/RealmController.cs @@ -388,6 +388,9 @@ public class RealmController( var pictureResult = await files.GetFileAsync(new GetFileRequest { Id = request.PictureId }); if (pictureResult is null) return BadRequest("Invalid picture id, unable to find the file on cloud."); realm.Picture = SnCloudFileReferenceObject.FromProtoValue(pictureResult); + + if (realm.IsPublic) + await files.SetFilePublicAsync(new SetFilePublicRequest { FileId = request.PictureId }); } if (request.BackgroundId is not null) @@ -395,6 +398,9 @@ public class RealmController( var backgroundResult = await files.GetFileAsync(new GetFileRequest { Id = request.BackgroundId }); if (backgroundResult is null) return BadRequest("Invalid background id, unable to find the file on cloud."); realm.Background = SnCloudFileReferenceObject.FromProtoValue(backgroundResult); + + if (realm.IsPublic) + await files.SetFilePublicAsync(new SetFilePublicRequest { FileId = request.BackgroundId }); } db.Realms.Add(realm); @@ -456,6 +462,9 @@ public class RealmController( if (pictureResult is null) return BadRequest("Invalid picture id, unable to find the file on cloud."); realm.Picture = SnCloudFileReferenceObject.FromProtoValue(pictureResult); + + if (realm.IsPublic) + await files.SetFilePublicAsync(new SetFilePublicRequest { FileId = request.PictureId }); } if (request.BackgroundId is not null) @@ -464,10 +473,14 @@ public class RealmController( if (backgroundResult is null) return BadRequest("Invalid background id, unable to find the file on cloud."); realm.Background = SnCloudFileReferenceObject.FromProtoValue(backgroundResult); + + if (realm.IsPublic) + await files.SetFilePublicAsync(new SetFilePublicRequest { FileId = request.BackgroundId }); } db.Realms.Update(realm); await db.SaveChangesAsync(); + await db.SaveChangesAsync(); als.CreateActionLogFromRequest( "realms.update", diff --git a/DysonNetwork.Shared/Proto/file.proto b/DysonNetwork.Shared/Proto/file.proto index dd486197..381c919b 100644 --- a/DysonNetwork.Shared/Proto/file.proto +++ b/DysonNetwork.Shared/Proto/file.proto @@ -70,6 +70,12 @@ service FileService { // Purge cache for a file rpc PurgeCache(PurgeCacheRequest) returns (google.protobuf.Empty); + + // Set file as publicly readable (anyone read permission) + rpc SetFilePublic(SetFilePublicRequest) returns (google.protobuf.Empty); + + // Remove public read permission + rpc UnsetFilePublic(UnsetFilePublicRequest) returns (google.protobuf.Empty); } // Request message for GetFile @@ -113,3 +119,11 @@ message DeleteFileRequest { message PurgeCacheRequest { string file_id = 1; } + +message SetFilePublicRequest { + string file_id = 1; +} + +message UnsetFilePublicRequest { + string file_id = 1; +} diff --git a/DysonNetwork.Sphere/Post/PostService.cs b/DysonNetwork.Sphere/Post/PostService.cs index f05adc0b..3bd7871a 100644 --- a/DysonNetwork.Sphere/Post/PostService.cs +++ b/DysonNetwork.Sphere/Post/PostService.cs @@ -163,6 +163,14 @@ public partial class PostService( post.Attachments = attachments .Select(id => post.Attachments.First(a => a.Id == id)) .ToList(); + + if (post.Visibility == Shared.Models.PostVisibility.Public) + { + foreach (var attachment in post.Attachments) + { + await files.SetFilePublicAsync(new SetFilePublicRequest { FileId = attachment.Id }); + } + } } if (tags is not null) @@ -326,9 +334,25 @@ public partial class PostService( throw new InvalidOperationException("Categories contains one or more categories that wasn't exists."); } + var oldVisibility = post.Visibility; db.Update(post); await db.SaveChangesAsync(); + if (post.Visibility == Shared.Models.PostVisibility.Public && oldVisibility != Shared.Models.PostVisibility.Public) + { + foreach (var attachment in post.Attachments) + { + await files.SetFilePublicAsync(new SetFilePublicRequest { FileId = attachment.Id }); + } + } + else if (oldVisibility == Shared.Models.PostVisibility.Public && post.Visibility != Shared.Models.PostVisibility.Public) + { + foreach (var attachment in post.Attachments) + { + await files.UnsetFilePublicAsync(new UnsetFilePublicRequest { FileId = attachment.Id }); + } + } + // Process link preview in the background to avoid delaying post update _ = Task.Run(async () => await CreateLinkPreviewAsync(post)); diff --git a/DysonNetwork.Sphere/Publisher/PublisherController.cs b/DysonNetwork.Sphere/Publisher/PublisherController.cs index 3092d3fd..bbaf823c 100644 --- a/DysonNetwork.Sphere/Publisher/PublisherController.cs +++ b/DysonNetwork.Sphere/Publisher/PublisherController.cs @@ -374,6 +374,8 @@ public class PublisherController( "Invalid picture id, unable to find the file on cloud." ); picture = SnCloudFileReferenceObject.FromProtoValue(queryResult); + + await files.SetFilePublicAsync(new SetFilePublicRequest { FileId = request.PictureId }); } if (request.BackgroundId is not null) @@ -386,6 +388,8 @@ public class PublisherController( "Invalid background id, unable to find the file on cloud." ); background = SnCloudFileReferenceObject.FromProtoValue(queryResult); + + await files.SetFilePublicAsync(new SetFilePublicRequest { FileId = request.BackgroundId }); } var publisher = await ps.CreateIndividualPublisher( @@ -471,6 +475,8 @@ public class PublisherController( "Invalid picture id, unable to find the file on cloud." ); picture = SnCloudFileReferenceObject.FromProtoValue(queryResult); + + await files.SetFilePublicAsync(new SetFilePublicRequest { FileId = request.PictureId }); } if (request.BackgroundId is not null) @@ -483,6 +489,8 @@ public class PublisherController( "Invalid background id, unable to find the file on cloud." ); background = SnCloudFileReferenceObject.FromProtoValue(queryResult); + + await files.SetFilePublicAsync(new SetFilePublicRequest { FileId = request.BackgroundId }); } var publisher = await ps.CreateOrganizationPublisher( @@ -569,6 +577,8 @@ public class PublisherController( var picture = SnCloudFileReferenceObject.FromProtoValue(queryResult); publisher.Picture = picture; + + await files.SetFilePublicAsync(new SetFilePublicRequest { FileId = request.PictureId }); } if (request.BackgroundId is not null) @@ -583,6 +593,8 @@ public class PublisherController( var background = SnCloudFileReferenceObject.FromProtoValue(queryResult); publisher.Background = background; + + await files.SetFilePublicAsync(new SetFilePublicRequest { FileId = request.BackgroundId }); } db.Update(publisher); diff --git a/DysonNetwork.Sphere/Sticker/StickerController.cs b/DysonNetwork.Sphere/Sticker/StickerController.cs index 225c4f6d..60196ce9 100644 --- a/DysonNetwork.Sphere/Sticker/StickerController.cs +++ b/DysonNetwork.Sphere/Sticker/StickerController.cs @@ -155,6 +155,8 @@ public class StickerController( return BadRequest("Icon not found."); pack.Icon = SnCloudFileReferenceObject.FromProtoValue(file); + + await files.SetFilePublicAsync(new SetFilePublicRequest { FileId = request.IconId }); } db.StickerPacks.Add(pack); @@ -197,6 +199,8 @@ public class StickerController( return BadRequest("Icon not found."); pack.Icon = SnCloudFileReferenceObject.FromProtoValue(file); + + await files.SetFilePublicAsync(new SetFilePublicRequest { FileId = request.IconId }); } db.StickerPacks.Update(pack);