using System.Text; using FozFc.Api.Configuration; using MongoDB.Bson; namespace FozFc.Api.Infrastructure.Storage; /// /// Stores uploads on the local disk (the container's uploads/ volume) and /// serves them from /uploads/<name>. /// public sealed class LocalFileStorage : IFileStorage { public const string UploadsDirectory = "uploads"; public const string PublicPathPrefix = "/uploads/"; private readonly AppOptions _options; public LocalFileStorage(AppOptions options) { _options = options; Directory.CreateDirectory(UploadsDirectory); } public async Task SaveAsync(Stream content, string originalFileName, CancellationToken ct = default) { var storedName = $"{ObjectId.GenerateNewId()}_{Sanitize(originalFileName)}"; var fullPath = Path.Combine(UploadsDirectory, storedName); await using (var file = File.Create(fullPath)) { await content.CopyToAsync(file, ct); } var url = $"{_options.PublicBaseUrl}{PublicPathPrefix}{storedName}"; return new StoredFile(url, $"{PublicPathPrefix}{storedName}", storedName); } private static string Sanitize(string fileName) { var name = Path.GetFileName(fileName); var builder = new StringBuilder(name.Length); foreach (var ch in name) { builder.Append(char.IsLetterOrDigit(ch) || ch is '.' or '-' or '_' ? ch : '_'); } var result = builder.ToString(); return result.Length == 0 ? "file" : result; } }