From 2a6ef8ca20da1834aa48d0de075cc2a1fc5f9f74 Mon Sep 17 00:00:00 2001 From: Khamraj Rohit Date: Wed, 30 Apr 2025 23:26:24 -0400 Subject: [PATCH] AobaService use transactions Added gRPC --- AobaCore/AobaCore.csproj | 3 +- AobaCore/AobaService.cs | 63 +++++++++++++++++++---- AobaCore/Extensions.cs | 1 - AobaCore/MediaService.cs | 34 ------------ AobaCore/Models/Media.cs | 2 +- AobaServer/AobaServer.csproj | 20 ++++--- AobaServer/Controllers/MediaController.cs | 16 +++--- AobaServer/Program.cs | 5 ++ AobaServer/Proto/Aoba.proto | 30 +++++++++++ AobaServer/Services/AobaRpcService.cs | 13 +++++ 10 files changed, 128 insertions(+), 59 deletions(-) delete mode 100644 AobaCore/MediaService.cs create mode 100644 AobaServer/Proto/Aoba.proto create mode 100644 AobaServer/Services/AobaRpcService.cs diff --git a/AobaCore/AobaCore.csproj b/AobaCore/AobaCore.csproj index 02d6c07..835ab6f 100644 --- a/AobaCore/AobaCore.csproj +++ b/AobaCore/AobaCore.csproj @@ -8,11 +8,10 @@ - + - diff --git a/AobaCore/AobaService.cs b/AobaCore/AobaService.cs index 51db6d2..30417e1 100644 --- a/AobaCore/AobaService.cs +++ b/AobaCore/AobaService.cs @@ -1,31 +1,76 @@ -using AobaV2.Models; +using AobaCore.Models; + +using MaybeError.Errors; using MongoDB.Bson; using MongoDB.Driver; +using MongoDB.Driver.GridFS; namespace AobaCore; public class AobaService(IMongoDatabase db) { private readonly IMongoCollection _media = db.GetCollection("media"); + private readonly GridFSBucket _gridFs = new(db); - public async Task GetMediaAsync(ObjectId id) + public async Task GetMediaAsync(ObjectId id, CancellationToken cancellationToken = default) { - return await _media.Find(m => m.Id == id).FirstOrDefaultAsync(); + return await _media.Find(m => m.Id == id).FirstOrDefaultAsync(cancellationToken); } - public Task AddMediaAsync(Media media) + public Task AddMediaAsync(Media media, CancellationToken cancellationToken = default) { - return _media.InsertOneAsync(media); + return _media.InsertOneAsync(media, null, cancellationToken); } - public Task IncrementViewCountAsync(ObjectId id) + public Task IncrementViewCountAsync(ObjectId id, CancellationToken cancellationToken = default) { - return _media.UpdateOneAsync(m => m.Id == id, Builders.Update.Inc(m => m.ViewCount, 1)); + return _media.UpdateOneAsync(m => m.Id == id, Builders.Update.Inc(m => m.ViewCount, 1), cancellationToken: cancellationToken); } - public Task IncrementFileViewCountAsync(ObjectId fileId) + public Task IncrementFileViewCountAsync(ObjectId fileId, CancellationToken cancellationToken = default) { - return _media.UpdateOneAsync(m => m.MediaId == fileId, Builders.Update.Inc(m => m.ViewCount, 1)); + return _media.UpdateOneAsync(m => m.MediaId == fileId, Builders.Update.Inc(m => m.ViewCount, 1), cancellationToken: cancellationToken); + } + + + public async Task> UploadFileAsync(Stream data, string filename, ObjectId owner, CancellationToken cancellationToken = default) + { + try + { + var fileId = await _gridFs.UploadFromStreamAsync(filename, data, cancellationToken: cancellationToken); + var media = new Media(fileId, filename, owner); + await AddMediaAsync(media, cancellationToken); + return media; + } + catch (Exception ex) + { + return ex; + } + } + + public async Task> GetFileStreamAsync(ObjectId id, bool seekable = false, CancellationToken cancellationToken = default) + { + try + { + return await _gridFs.OpenDownloadStreamAsync(id, new GridFSDownloadOptions { Seekable = seekable }, cancellationToken); + } + catch (GridFSException ex) + { + return ex; + } + } + + public async Task DeleteFileAsync(ObjectId fileId, CancellationToken cancellationToken = default) + { + try + { + await _gridFs.DeleteAsync(fileId, cancellationToken); + await _media.DeleteOneAsync(m => m.MediaId == fileId, cancellationToken); + } + catch (GridFSFileNotFoundException) + { + //ignore if file was not found + } } } diff --git a/AobaCore/Extensions.cs b/AobaCore/Extensions.cs index f04dd0c..405ea6d 100644 --- a/AobaCore/Extensions.cs +++ b/AobaCore/Extensions.cs @@ -23,7 +23,6 @@ public static class Extensions services.AddSingleton(dbClient); services.AddSingleton(db); services.AddSingleton(); - services.AddSingleton(); return services; } } diff --git a/AobaCore/MediaService.cs b/AobaCore/MediaService.cs deleted file mode 100644 index 0573ba8..0000000 --- a/AobaCore/MediaService.cs +++ /dev/null @@ -1,34 +0,0 @@ -using AobaV2.Models; - -using MaybeError.Errors; - -using MongoDB.Bson; -using MongoDB.Driver; -using MongoDB.Driver.GridFS; - - -namespace AobaCore; -public class MediaService(IMongoDatabase db, AobaService aobaService) -{ - private readonly GridFSBucket _gridFs = new(db); - - public async Task> UploadMediaAsync(Stream data, string filename, ObjectId owner, CancellationToken cancellationToken = default) - { - var fileId = await _gridFs.UploadFromStreamAsync(filename, data, cancellationToken: cancellationToken); - var media = new Media(fileId, filename, owner); - await aobaService.AddMediaAsync(media); - return media; - } - - public async Task>> GetMediaStreamAsync(ObjectId id, bool seekable = false) - { - try - { - return await _gridFs.OpenDownloadStreamAsync(id, new GridFSDownloadOptions { Seekable = seekable }); - } - catch (GridFSException ex) - { - return new ExceptionError(ex); - } - } -} diff --git a/AobaCore/Models/Media.cs b/AobaCore/Models/Media.cs index 0c140a6..5d178b5 100644 --- a/AobaCore/Models/Media.cs +++ b/AobaCore/Models/Media.cs @@ -1,7 +1,7 @@ using MongoDB.Bson; using MongoDB.Bson.Serialization.Attributes; -namespace AobaV2.Models; +namespace AobaCore.Models; [BsonIgnoreExtraElements] public class Media diff --git a/AobaServer/AobaServer.csproj b/AobaServer/AobaServer.csproj index 2e49415..ffcabe8 100644 --- a/AobaServer/AobaServer.csproj +++ b/AobaServer/AobaServer.csproj @@ -9,21 +9,29 @@ + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + - - + - + - - - + + + + + + + diff --git a/AobaServer/Controllers/MediaController.cs b/AobaServer/Controllers/MediaController.cs index c1124e4..2d869b7 100644 --- a/AobaServer/Controllers/MediaController.cs +++ b/AobaServer/Controllers/MediaController.cs @@ -10,20 +10,24 @@ using MongoDB.Driver; namespace AobaServer.Controllers; [Route("/m")] -public class MediaController(MediaService mediaService, AobaService aobaService, ILogger logger) : Controller +public class MediaController(AobaService aobaService, ILogger logger) : Controller { [HttpGet("{id}")] [ResponseCache(Duration = int.MaxValue)] - public async Task MediaAsync(ObjectId id) + public async Task MediaAsync(ObjectId id, [FromServices] MongoClient client, CancellationToken cancellationToken) { - var file = await mediaService.GetMediaStreamAsync(id); + using var session = await client.StartSessionAsync(cancellationToken: cancellationToken); + session.StartTransaction(); + var file = await aobaService.GetFileStreamAsync(id, cancellationToken: cancellationToken); if (file.HasError) { + await session.AbortTransactionAsync(cancellationToken: cancellationToken); logger.LogError(file.Error.Exception, "Failed to load media stream"); return NotFound(); } + await session.CommitTransactionAsync(cancellationToken: cancellationToken); var mime = MimeTypesMap.GetMimeType(file.Value.FileInfo.Filename); - _ = aobaService.IncrementFileViewCountAsync(id); + _ = aobaService.IncrementFileViewCountAsync(id, cancellationToken); return File(file, mime, true); } @@ -35,9 +39,9 @@ public class MediaController(MediaService mediaService, AobaService aobaService, /// /// [HttpGet("/i/{id}/{*rest}")] - public async Task LegacyRedirectAsync(ObjectId id, string rest, [FromServices] AobaService aoba) + public async Task LegacyRedirectAsync(ObjectId id, string rest, CancellationToken cancellationToken) { - var media = await aoba.GetMediaAsync(id); + var media = await aobaService.GetMediaAsync(id, cancellationToken); if (media == null) return NotFound(); return LocalRedirectPermanent($"/m/{media.MediaId}/{rest}"); diff --git a/AobaServer/Program.cs b/AobaServer/Program.cs index 24205d5..1c4a5d3 100644 --- a/AobaServer/Program.cs +++ b/AobaServer/Program.cs @@ -4,6 +4,7 @@ using AobaServer; using AobaServer.Auth; using AobaServer.Middleware; using AobaServer.Models; +using AobaServer.Services; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.JwtBearer; @@ -16,6 +17,7 @@ var builder = WebApplication.CreateBuilder(args); builder.Services.AddControllers(opt => opt.ModelBinderProviders.Add(new BsonIdModelBinderProvider())); builder.Services.AddObersability(builder.Configuration); +builder.Services.AddGrpc(); var authInfo = AuthInfo.LoadOrCreate("Auth.json", "aobaV2", "aoba"); builder.Services.AddSingleton(authInfo); @@ -92,6 +94,9 @@ app.UseAuthorization(); app.MapControllers(); app.MapObserability(); +app.MapGrpcService(); + + app.Run(); diff --git a/AobaServer/Proto/Aoba.proto b/AobaServer/Proto/Aoba.proto new file mode 100644 index 0000000..91e62ed --- /dev/null +++ b/AobaServer/Proto/Aoba.proto @@ -0,0 +1,30 @@ +syntax = "proto3"; + + +service AobaRPC { + rpc GetMedia (Id) returns (MediaModel); +} + +message Id{ + string idString = 1; +} + +message MediaModel { + int32 version = 1; + Id id = 2; + string mediaId = 3; + string fileName = 4; + MediaType mediaType = 5; + string ext = 6; + int32 viewCount = 7; + Id owner = 8; +} + +enum MediaType{ + Image = 0; + Audio = 1; + Video = 2; + Text = 3; + Code = 4; + Raw = 5; +} \ No newline at end of file diff --git a/AobaServer/Services/AobaRpcService.cs b/AobaServer/Services/AobaRpcService.cs new file mode 100644 index 0000000..c915eff --- /dev/null +++ b/AobaServer/Services/AobaRpcService.cs @@ -0,0 +1,13 @@ +using AobaCore; + +using Grpc.Core; + +namespace AobaServer.Services; + +public class AobaRpcService(AobaService aobaService) : AobaRPC.AobaRPCBase +{ + public override Task GetMedia(Id request, ServerCallContext context) + { + return base.GetMedia(request, context); + } +}