This commit is contained in:
2025-05-01 22:21:57 -04:00
10 changed files with 128 additions and 59 deletions

View File

@@ -8,11 +8,10 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.4" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.4" />
<PackageReference Include="MaybeError" Version="1.0.6" /> <PackageReference Include="MaybeError" Version="1.1.0" />
<PackageReference Include="MongoDB.Analyzer" Version="1.5.0" /> <PackageReference Include="MongoDB.Analyzer" Version="1.5.0" />
<PackageReference Include="MongoDB.Driver" Version="3.3.0" /> <PackageReference Include="MongoDB.Driver" Version="3.3.0" />
<PackageReference Include="MongoDB.Driver.Core.Extensions.DiagnosticSources" Version="2.0.0" /> <PackageReference Include="MongoDB.Driver.Core.Extensions.DiagnosticSources" Version="2.0.0" />
<PackageReference Include="MongoDB.Driver.GridFS" Version="2.30.0" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -1,31 +1,76 @@
using AobaV2.Models; using AobaCore.Models;
using MaybeError.Errors;
using MongoDB.Bson; using MongoDB.Bson;
using MongoDB.Driver; using MongoDB.Driver;
using MongoDB.Driver.GridFS;
namespace AobaCore; namespace AobaCore;
public class AobaService(IMongoDatabase db) public class AobaService(IMongoDatabase db)
{ {
private readonly IMongoCollection<Media> _media = db.GetCollection<Media>("media"); private readonly IMongoCollection<Media> _media = db.GetCollection<Media>("media");
private readonly GridFSBucket _gridFs = new(db);
public async Task<Media?> GetMediaAsync(ObjectId id) public async Task<Media?> 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<Media>.Update.Inc(m => m.ViewCount, 1)); return _media.UpdateOneAsync(m => m.Id == id, Builders<Media>.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<Media>.Update.Inc(m => m.ViewCount, 1)); return _media.UpdateOneAsync(m => m.MediaId == fileId, Builders<Media>.Update.Inc(m => m.ViewCount, 1), cancellationToken: cancellationToken);
}
public async Task<Maybe<Media>> 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<MaybeEx<GridFSDownloadStream, GridFSException>> 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
}
} }
} }

View File

@@ -23,7 +23,6 @@ public static class Extensions
services.AddSingleton(dbClient); services.AddSingleton(dbClient);
services.AddSingleton<IMongoDatabase>(db); services.AddSingleton<IMongoDatabase>(db);
services.AddSingleton<AobaService>(); services.AddSingleton<AobaService>();
services.AddSingleton<MediaService>();
return services; return services;
} }
} }

View File

@@ -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<Maybe<Media>> 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<Maybe<GridFSDownloadStream, ExceptionError<GridFSException>>> GetMediaStreamAsync(ObjectId id, bool seekable = false)
{
try
{
return await _gridFs.OpenDownloadStreamAsync(id, new GridFSDownloadOptions { Seekable = seekable });
}
catch (GridFSException ex)
{
return new ExceptionError<GridFSException>(ex);
}
}
}

View File

@@ -1,7 +1,7 @@
using MongoDB.Bson; using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes; using MongoDB.Bson.Serialization.Attributes;
namespace AobaV2.Models; namespace AobaCore.Models;
[BsonIgnoreExtraElements] [BsonIgnoreExtraElements]
public class Media public class Media

View File

@@ -9,21 +9,29 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Grpc.AspNetCore" Version="2.71.0" />
<PackageReference Include="Grpc.Tools" Version="2.71.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Isopoh.Cryptography.Argon2" Version="2.0.0" /> <PackageReference Include="Isopoh.Cryptography.Argon2" Version="2.0.0" />
<PackageReference Include="MaybeError" Version="1.0.6" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.4" /> <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.4" />
<PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" Version="8.8.0" /> <PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" Version="8.9.0" />
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.21.2" /> <PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.21.2" />
<PackageReference Include="MimeTypesMap" Version="1.0.9" /> <PackageReference Include="MimeTypesMap" Version="1.0.9" />
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.9.0" /> <PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.12.0" />
<PackageReference Include="OpenTelemetry.Exporter.Prometheus.AspNetCore" Version="1.9.0-beta.2" /> <PackageReference Include="OpenTelemetry.Exporter.Prometheus.AspNetCore" Version="1.9.0-beta.2" />
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.9.0" /> <PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.12.0" />
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.9.0" /> <PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.11.1" />
<PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.9.0" /> <PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.11.1" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\AobaCore\AobaCore.csproj" /> <ProjectReference Include="..\AobaCore\AobaCore.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Protobuf Include="Proto\Aoba.proto"></Protobuf>
</ItemGroup>
</Project> </Project>

View File

@@ -10,20 +10,24 @@ using MongoDB.Driver;
namespace AobaServer.Controllers; namespace AobaServer.Controllers;
[Route("/m")] [Route("/m")]
public class MediaController(MediaService mediaService, AobaService aobaService, ILogger<MediaController> logger) : Controller public class MediaController(AobaService aobaService, ILogger<MediaController> logger) : Controller
{ {
[HttpGet("{id}")] [HttpGet("{id}")]
[ResponseCache(Duration = int.MaxValue)] [ResponseCache(Duration = int.MaxValue)]
public async Task<IActionResult> MediaAsync(ObjectId id) public async Task<IActionResult> 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) if (file.HasError)
{ {
await session.AbortTransactionAsync(cancellationToken: cancellationToken);
logger.LogError(file.Error.Exception, "Failed to load media stream"); logger.LogError(file.Error.Exception, "Failed to load media stream");
return NotFound(); return NotFound();
} }
await session.CommitTransactionAsync(cancellationToken: cancellationToken);
var mime = MimeTypesMap.GetMimeType(file.Value.FileInfo.Filename); var mime = MimeTypesMap.GetMimeType(file.Value.FileInfo.Filename);
_ = aobaService.IncrementFileViewCountAsync(id); _ = aobaService.IncrementFileViewCountAsync(id, cancellationToken);
return File(file, mime, true); return File(file, mime, true);
} }
@@ -35,9 +39,9 @@ public class MediaController(MediaService mediaService, AobaService aobaService,
/// <param name="aoba"></param> /// <param name="aoba"></param>
/// <returns></returns> /// <returns></returns>
[HttpGet("/i/{id}/{*rest}")] [HttpGet("/i/{id}/{*rest}")]
public async Task<IActionResult> LegacyRedirectAsync(ObjectId id, string rest, [FromServices] AobaService aoba) public async Task<IActionResult> LegacyRedirectAsync(ObjectId id, string rest, CancellationToken cancellationToken)
{ {
var media = await aoba.GetMediaAsync(id); var media = await aobaService.GetMediaAsync(id, cancellationToken);
if (media == null) if (media == null)
return NotFound(); return NotFound();
return LocalRedirectPermanent($"/m/{media.MediaId}/{rest}"); return LocalRedirectPermanent($"/m/{media.MediaId}/{rest}");

View File

@@ -4,6 +4,7 @@ using AobaServer;
using AobaServer.Auth; using AobaServer.Auth;
using AobaServer.Middleware; using AobaServer.Middleware;
using AobaServer.Models; using AobaServer.Models;
using AobaServer.Services;
using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.JwtBearer; 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.AddControllers(opt => opt.ModelBinderProviders.Add(new BsonIdModelBinderProvider()));
builder.Services.AddObersability(builder.Configuration); builder.Services.AddObersability(builder.Configuration);
builder.Services.AddGrpc();
var authInfo = AuthInfo.LoadOrCreate("Auth.json", "aobaV2", "aoba"); var authInfo = AuthInfo.LoadOrCreate("Auth.json", "aobaV2", "aoba");
builder.Services.AddSingleton(authInfo); builder.Services.AddSingleton(authInfo);
@@ -92,6 +94,9 @@ app.UseAuthorization();
app.MapControllers(); app.MapControllers();
app.MapObserability(); app.MapObserability();
app.MapGrpcService<AobaRpcService>();
app.Run(); app.Run();

View File

@@ -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;
}

View File

@@ -0,0 +1,13 @@
using AobaCore;
using Grpc.Core;
namespace AobaServer.Services;
public class AobaRpcService(AobaService aobaService) : AobaRPC.AobaRPCBase
{
public override Task<MediaModel> GetMedia(Id request, ServerCallContext context)
{
return base.GetMedia(request, context);
}
}