[FA-5] FileService setup, build scripts tweaked to be easier to maintain

This commit is contained in:
gamer147
2025-11-22 22:36:44 -05:00
parent 1adbb955cf
commit 573a0f6e3f
19 changed files with 519 additions and 103 deletions

View File

@@ -0,0 +1,49 @@
using System.Web;
using Amazon.S3;
using Amazon.S3.Model;
using FictionArchive.Service.FileService.Models;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
namespace FictionArchive.Service.FileService.Controllers
{
[Route("api/{*path}")]
[ApiController]
public class S3ProxyController : ControllerBase
{
private readonly AmazonS3Client _amazonS3Client;
private readonly S3Configuration _s3Configuration;
public S3ProxyController(AmazonS3Client amazonS3Client, IOptions<S3Configuration> s3Configuration)
{
_amazonS3Client = amazonS3Client;
_s3Configuration = s3Configuration.Value;
}
[HttpGet]
public async Task<IActionResult> Get(string path)
{
var decodedPath = HttpUtility.UrlDecode(path);
try
{
var s3Response = await _amazonS3Client.GetObjectAsync(new GetObjectRequest()
{
BucketName = _s3Configuration.Bucket,
Key = decodedPath
});
return new FileStreamResult(s3Response.ResponseStream, s3Response.Headers.ContentType);
}
catch (AmazonS3Exception e)
{
if (e.Message == "Key not found")
{
return NotFound();
}
throw;
}
}
}
}

View File

@@ -0,0 +1,23 @@
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
USER $APP_UID
WORKDIR /app
EXPOSE 8080
EXPOSE 8081
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
ARG BUILD_CONFIGURATION=Release
WORKDIR /src
COPY ["FictionArchive.Service.ImageService/FictionArchive.Service.ImageService.csproj", "FictionArchive.Service.ImageService/"]
RUN dotnet restore "FictionArchive.Service.ImageService/FictionArchive.Service.ImageService.csproj"
COPY . .
WORKDIR "/src/FictionArchive.Service.ImageService"
RUN dotnet build "./FictionArchive.Service.ImageService.csproj" -c $BUILD_CONFIGURATION -o /app/build
FROM build AS publish
ARG BUILD_CONFIGURATION=Release
RUN dotnet publish "./FictionArchive.Service.ImageService.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "FictionArchive.Service.ImageService.dll"]

View File

@@ -0,0 +1,30 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
</PropertyGroup>
<ItemGroup>
<Content Include="..\.dockerignore">
<Link>.dockerignore</Link>
</Content>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\FictionArchive.Service.Shared\FictionArchive.Service.Shared.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="AWSSDK.S3" Version="4.0.13.1" />
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="9.0.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="10.0.1" />
</ItemGroup>
<ItemGroup>
<Folder Include="Controllers\" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,10 @@
using FictionArchive.Service.Shared.Services.EventBus;
namespace FictionArchive.Service.FileService.Models.IntegrationEvents;
public class FileUploadRequestCreatedEvent : IIntegrationEvent
{
public Guid RequestId { get; set; }
public string FilePath { get; set; }
public byte[] FileData { get; set; }
}

View File

@@ -0,0 +1,22 @@
using FictionArchive.Common.Enums;
using FictionArchive.Service.Shared.Services.EventBus;
namespace FictionArchive.Service.FileService.Models.IntegrationEvents;
public class FileUploadRequestStatusUpdateEvent : IIntegrationEvent
{
public Guid RequestId { get; set; }
public RequestStatus Status { get; set; }
#region Success
public string? FileAccessUrl { get; set; }
#endregion
#region Failure
public string? ErrorMessage { get; set; }
#endregion
}

View File

@@ -0,0 +1,6 @@
namespace FictionArchive.Service.FileService.Models;
public class ProxyConfiguration
{
public string BaseUrl { get; set; }
}

View File

@@ -0,0 +1,9 @@
namespace FictionArchive.Service.FileService.Models;
public class S3Configuration
{
public string Url { get; set; }
public string Bucket { get; set; }
public string AccessKey { get; set; }
public string SecretKey { get; set; }
}

View File

@@ -0,0 +1,66 @@
using Amazon.Runtime;
using Amazon.S3;
using FictionArchive.Common.Extensions;
using FictionArchive.Service.FileService.Models;
using FictionArchive.Service.Shared.Extensions;
using FictionArchive.Service.Shared.Services.EventBus.Implementations;
using Microsoft.Extensions.Options;
namespace FictionArchive.Service.FileService;
public class Program
{
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
builder.AddLocalAppsettings();
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddHealthChecks();
#region Event Bus
builder.Services.AddRabbitMQ(opt =>
{
builder.Configuration.GetSection("RabbitMQ").Bind(opt);
});
#endregion
builder.Services.Configure<ProxyConfiguration>(builder.Configuration.GetSection("ProxyConfiguration"));
// Add S3 Client
builder.Services.Configure<S3Configuration>(builder.Configuration.GetSection("S3"));
builder.Services.AddSingleton<AmazonS3Client>(provider =>
{
var config = provider.GetRequiredService<IOptions<S3Configuration>>().Value;
var s3Config = new AmazonS3Config
{
ServiceURL = config.Url, // Garage endpoint
ForcePathStyle = true, // REQUIRED for Garage
AuthenticationRegion = "garage"
};
return new AmazonS3Client(
new BasicAWSCredentials(config.AccessKey, config.SecretKey),
s3Config);
});
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.MapHealthChecks("/healthz");
app.MapControllers();
app.Run();
}
}

View File

@@ -0,0 +1,39 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:5546",
"sslPort": 44373
}
},
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "http://localhost:5057",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "https://localhost:7247;http://localhost:5057",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

View File

@@ -0,0 +1,57 @@
using Amazon.S3;
using Amazon.S3.Model;
using FictionArchive.Common.Enums;
using FictionArchive.Service.FileService.Models;
using FictionArchive.Service.FileService.Models.IntegrationEvents;
using FictionArchive.Service.Shared.Services.EventBus;
using Microsoft.Extensions.Options;
namespace FictionArchive.Service.FileService.Services.EventHandlers;
public class FileUploadRequestCreatedEventHandler : IIntegrationEventHandler<FileUploadRequestCreatedEvent>
{
private readonly ILogger<FileUploadRequestCreatedEventHandler> _logger;
private readonly AmazonS3Client _amazonS3Client;
private readonly IEventBus _eventBus;
private readonly S3Configuration _s3Configuration;
private readonly ProxyConfiguration _proxyConfiguration;
public FileUploadRequestCreatedEventHandler(ILogger<FileUploadRequestCreatedEventHandler> logger, AmazonS3Client amazonS3Client, IEventBus eventBus, IOptions<S3Configuration> s3Configuration, IOptions<ProxyConfiguration> proxyConfiguration)
{
_logger = logger;
_amazonS3Client = amazonS3Client;
_eventBus = eventBus;
_proxyConfiguration = proxyConfiguration.Value;
_s3Configuration = s3Configuration.Value;
}
public async Task Handle(FileUploadRequestCreatedEvent @event)
{
var putObjectRequest = new PutObjectRequest();
putObjectRequest.BucketName = _s3Configuration.Bucket;
putObjectRequest.Key = @event.FilePath;
using MemoryStream memoryStream = new MemoryStream(@event.FileData);
putObjectRequest.InputStream = memoryStream;
var s3Response = await _amazonS3Client.PutObjectAsync(putObjectRequest);
if (s3Response.HttpStatusCode != System.Net.HttpStatusCode.OK)
{
_logger.LogError("An error occurred while uploading file to S3. Response code: {responsecode}", s3Response.HttpStatusCode);
await _eventBus.Publish(new FileUploadRequestStatusUpdateEvent()
{
RequestId = @event.RequestId,
Status = RequestStatus.Failed,
ErrorMessage = "An error occurred while uploading file to S3."
});
return;
}
await _eventBus.Publish(new FileUploadRequestStatusUpdateEvent()
{
Status = RequestStatus.Success,
RequestId = @event.RequestId,
FileAccessUrl = _proxyConfiguration.BaseUrl + "/" + @event.FilePath
});
}
}

View File

@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

View File

@@ -0,0 +1,22 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"Proxy": {
"BaseUrl": "https://localhost:7247/api"
},
"RabbitMQ": {
"ConnectionString": "amqp://localhost",
"ClientIdentifier": "NovelService"
},
"S3": {
"Url": "https://s3.orfl.xyz",
"Bucket": "fictionarchive",
"AccessKey": "REPLACE_ME",
"SecretKey": "REPLACE_ME"
},
"AllowedHosts": "*"
}