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