diff --git a/src/Spd.Manager.Licence/PersonalLicenceAppManager.cs b/src/Spd.Manager.Licence/PersonalLicenceAppManager.cs index 663adc2e0..b7798d7e5 100644 --- a/src/Spd.Manager.Licence/PersonalLicenceAppManager.cs +++ b/src/Spd.Manager.Licence/PersonalLicenceAppManager.cs @@ -207,7 +207,23 @@ public async Task Handle(AnonymousWorkerLicenceA saveCmd.ApplicationStatusEnum = ApplicationStatusEnum.PaymentPending; var response = await _licenceAppRepository.SaveLicenceApplicationAsync(saveCmd, ct); - //todo: add file copying here. + //add photo file copying here. + if (cmd.LicenceAnonymousRequest.OriginalApplicationId == null) + throw new ArgumentException("replacement request must have original application id"); + var photos = await _documentRepository.QueryAsync( + new DocumentQry( + ApplicationId: cmd.LicenceAnonymousRequest.OriginalApplicationId, + FileType: DocumentTypeEnum.Photograph), + ct); + if (photos.Items.Any()) + { + foreach (var photo in photos.Items) + { + await _documentRepository.ManageAsync( + new CopyDocumentCmd(photo.DocumentUrlId, response.LicenceAppId, response.ContactId), + ct); + } + } return new WorkerLicenceAppUpsertResponse { LicenceAppId = response.LicenceAppId }; } #endregion diff --git a/src/Spd.Presentation.Dynamics/Controllers/FileStorageController.cs b/src/Spd.Presentation.Dynamics/Controllers/FileStorageController.cs index ec958fe0e..c2c0d75df 100644 --- a/src/Spd.Presentation.Dynamics/Controllers/FileStorageController.cs +++ b/src/Spd.Presentation.Dynamics/Controllers/FileStorageController.cs @@ -1,5 +1,6 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using Spd.Presentation.Dynamics.Helper; using Spd.Presentation.Dynamics.Models; using Spd.Utilities.FileStorage; using Spd.Utilities.Shared; @@ -66,7 +67,7 @@ public async Task UploadFileAsync( }; FileTag fileTag = new() { - Tags = GetTagsFromStr(tags, classification) + Tags = FileStorageHelper.GetTagsFromStr(tags, classification) }; await _storageService.HandleCommand( new UploadFileCommand(fileId.ToString(), folder, file, fileTag), @@ -116,7 +117,7 @@ public async Task DownloadFileAsync( HttpContext.Response.Headers.Add(SpdHeaderNames.HEADER_FILE_CLASSIFICATION, result.FileTag.Tags.SingleOrDefault(t => t.Key == SpdHeaderNames.HEADER_FILE_CLASSIFICATION)?.Value); - string tagStr = GetStrFromTags(result.FileTag.Tags); + string tagStr = FileStorageHelper.GetStrFromTags(result.FileTag.Tags); if (!string.IsNullOrWhiteSpace(tagStr)) HttpContext.Response.Headers.Add(SpdHeaderNames.HEADER_FILE_TAG, tagStr); } @@ -164,7 +165,7 @@ public async Task UpdateTagsAsync( { FileTag fileTag = new() { - Tags = GetTagsFromStr(tags, classification), + Tags = FileStorageHelper.GetTagsFromStr(tags, classification), }; await _storageService.HandleCommand( new UpdateTagsCommand(fileId.ToString(), folder, fileTag), @@ -173,40 +174,30 @@ await _storageService.HandleCommand( } } - private Tag[] GetTagsFromStr(string? tagStr, string classification) + /// + /// copy file for one location in main bucket to another location in the same main bucket + /// + /// + /// + /// + [HttpPost] + [Route("api/files/copy-file")] + public async Task CopyFileAsync( + [FromBody] CopyFileRequest copyFileRequest, + CancellationToken ct) { - try - { - List taglist = new() { new Tag(SpdHeaderNames.HEADER_FILE_CLASSIFICATION, classification) }; + //check if file already exists + FileMetadataQueryResult queryResult = (FileMetadataQueryResult)await _storageService.HandleQuery( + new FileMetadataQuery { Key = copyFileRequest.SourceKey }, + ct); + bool fileExists = queryResult != null; - if (!string.IsNullOrWhiteSpace(tagStr)) - { - string[] tags = tagStr.Split(','); - foreach (string tag in tags) - { - string[] strs = tag.Split('='); - if (strs.Length != 2) throw new OutOfRangeException(HttpStatusCode.BadRequest, $"Invalid {SpdHeaderNames.HEADER_FILE_TAG} string"); - taglist.Add( - new Tag(strs[0], strs[1]) - ); - } - } - return taglist.ToArray(); - } - catch - { - throw new ApiException(HttpStatusCode.BadRequest, $"Invalid {SpdHeaderNames.HEADER_FILE_TAG} string"); - } - } + if (!fileExists) { return NotFound(); } - private string GetStrFromTags(IEnumerable tags) - { - List tagStrlist = new(); - foreach (Tag t in tags) - { - if (t.Key != SpdHeaderNames.HEADER_FILE_CLASSIFICATION) - tagStrlist.Add($"{t.Key}={t.Value}"); - } - return string.Join(",", tagStrlist); + await _storageService.HandleCommand( + new CopyFileCommand(copyFileRequest.SourceKey, null, copyFileRequest.DestKey, null), + ct); + + return StatusCode(StatusCodes.Status201Created); } } diff --git a/src/Spd.Presentation.Dynamics/Controllers/TransientFileStorageController.cs b/src/Spd.Presentation.Dynamics/Controllers/TransientFileStorageController.cs new file mode 100644 index 000000000..da25af258 --- /dev/null +++ b/src/Spd.Presentation.Dynamics/Controllers/TransientFileStorageController.cs @@ -0,0 +1,205 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Spd.Presentation.Dynamics.Helper; +using Spd.Presentation.Dynamics.Models; +using Spd.Utilities.FileStorage; +using Spd.Utilities.Shared; +using Spd.Utilities.Shared.Exceptions; +using System.ComponentModel.DataAnnotations; +using System.Net; +using File = Spd.Utilities.FileStorage.File; + +namespace Spd.Presentation.Dynamics.Controllers; + +/// +/// For upload and download file +/// +[Authorize] +public class TransientFileStorageController : SpdControllerBase +{ + private readonly ITransientFileStorageService _storageService; + public TransientFileStorageController(ITransientFileStorageService storageService) : base() + { + _storageService = storageService; + } + + /// + /// Upload or overwrite file to transient bucket + /// If the file guid exists, the file content is overwritten by the new file + /// all the tags must be sent in the requests, tags that do not exist will be removed from the file storage + /// The maximum file size would be 30M. + /// + /// File with selected file data. + /// the GUID of the file + /// mandatory, must contain only alphanumeric characters + /// must be in the form key=value, key must contain only alphanumeric characters. Multi tags should be like: tag1=a,tag2=b + /// be used to allocate the file in a specific folder in storage, if not specified, defaults to / (root folder) + /// cancellation token, generated by dotnetcore + /// Ok, the file was updated + /// Created, the file was created + /// Bad Request, classification header is missing,form data is invalid or file id is not a valid guid + /// + /// + [HttpPost] + [Route("api/transient-files/{fileId}")] + public async Task UploadFileAsync( + [FromForm] UploadFileRequest request, + [FromRoute] Guid fileId, + [FromHeader(Name = "file-classification")][Required] string classification, + [FromHeader(Name = "file-tag")] string? tags, + [FromHeader(Name = "file-folder")] string? folder, + CancellationToken ct) + { + //check if file already exists + FileMetadataQueryResult queryResult = (FileMetadataQueryResult)await _storageService.HandleQuery( + new FileMetadataQuery { Key = fileId.ToString(), Folder = folder }, + ct); + bool fileExists = queryResult != null; + + //upload file + using var ms = new MemoryStream(); + await request.File.CopyToAsync(ms, ct); + File file = new() + { + FileName = request.File.FileName, + ContentType = request.File.ContentType, + Content = ms.ToArray() + }; + FileTag fileTag = new() + { + Tags = FileStorageHelper.GetTagsFromStr(tags, classification) + }; + await _storageService.HandleCommand( + new UploadFileCommand(fileId.ToString(), folder, file, fileTag), + ct); + + return fileExists ? Ok() : StatusCode(StatusCodes.Status201Created); + } + + /// + /// Download the file with fileId and folder name from to transient bucket. + /// If a file is expected to be in a folder, the client must pass the correct folder name in the request header, + /// otherwise no file will found; the default header value is the root folder + /// + /// the GUID of the file + /// be used to allocate the file in a specific folder in storage, if not specified, defaults to / (root folder) + /// cancellation token, generated by dotnetcore + /// + /// Ok + /// Not Found + /// Bad Request, file id is not a valid guid + [HttpGet] + [Route("api/transient-files/{fileId}")] + public async Task DownloadFileAsync( + Guid fileId, + [FromHeader(Name = "file-folder")] string? folder, + CancellationToken ct) + { + var queryResult = (FileMetadataQueryResult)await _storageService.HandleQuery( + new FileMetadataQuery { Key = fileId.ToString(), Folder = folder }, + ct); + + bool fileExists = queryResult != null; + if (!fileExists) + { + throw new ApiException(HttpStatusCode.NotFound, "cannot find the file"); + } + + FileQueryResult result = (FileQueryResult)await _storageService.HandleQuery( + new FileQuery { Key = fileId.ToString(), Folder = folder }, + ct); + + var content = new MemoryStream(result.File.Content); + var contentType = result.File.ContentType ?? "application/octet-stream"; + + if (result.FileTag != null) + { + HttpContext.Response.Headers.Add(SpdHeaderNames.HEADER_FILE_CLASSIFICATION, + result.FileTag.Tags.SingleOrDefault(t => t.Key == SpdHeaderNames.HEADER_FILE_CLASSIFICATION)?.Value); + + string tagStr = FileStorageHelper.GetStrFromTags(result.FileTag.Tags); + if (!string.IsNullOrWhiteSpace(tagStr)) + HttpContext.Response.Headers.Add(SpdHeaderNames.HEADER_FILE_TAG, tagStr); + } + + if (!string.IsNullOrWhiteSpace(folder)) + HttpContext.Response.Headers.Add(SpdHeaderNames.HEADER_FILE_FOLDER, folder); + + return new FileStreamResult(content, contentType); + } + + /// + /// To transient bucket, Only updates the tags passed in the header and will not affect the content of the file + /// any tags not present in the request will be deleted + /// tags must be in the form key=value, key must contain only alphanumeric characters(i.e.tag1= value1) + /// classification must contain only alphanumeric characters(i.e.confidential/internal/public) + /// + /// the GUID of the file + /// mandatory, must contain only alphanumeric characters + /// multi tags should be like: tag1=a,tag2=b + /// be used to allocate the file in a specific folder in storage, if not specified, defaults to / (root folder) + /// + /// + /// Ok + /// Not Found + /// Bad Request, file id is not a valid guid + [HttpPost] + [Route("api/transient-files/{fileId}/tags")] + public async Task UpdateTagsAsync( + [FromRoute] Guid fileId, + [FromHeader(Name = "file-classification")][Required] string classification, + [FromHeader(Name = "file-tag")] string? tags, + [FromHeader(Name = "file-folder")] string? folder, + CancellationToken ct) + { + //check if file already exists + var queryResult = (FileMetadataQueryResult)await _storageService.HandleQuery( + new FileMetadataQuery { Key = fileId.ToString(), Folder = folder }, + ct); + bool fileExists = queryResult != null; + if (!fileExists) + { + return NotFound(); + } + else + { + FileTag fileTag = new() + { + Tags = FileStorageHelper.GetTagsFromStr(tags, classification), + }; + await _storageService.HandleCommand( + new UpdateTagsCommand(fileId.ToString(), folder, fileTag), + ct); + return Ok(); + } + } + + /// + /// copy file for one location in transient bucket to another location in the same transient bucket + /// + /// + /// + /// + [HttpPost] + [Route("api/transient-files/copy-file")] + public async Task CopyFileAsync( + [FromBody] CopyFileRequest copyFileRequest, + CancellationToken ct) + { + //check if file already exists + FileMetadataQueryResult queryResult = (FileMetadataQueryResult)await _storageService.HandleQuery( + new FileMetadataQuery { Key = copyFileRequest.SourceKey }, + ct); + bool fileExists = queryResult != null; + + if (!fileExists) { return NotFound(); } + + await _storageService.HandleCommand( + new CopyFileCommand(copyFileRequest.SourceKey, null, copyFileRequest.DestKey, null), + ct); + + return StatusCode(StatusCodes.Status201Created); + } + + +} diff --git a/src/Spd.Presentation.Dynamics/Helper/FileStorageHelper.cs b/src/Spd.Presentation.Dynamics/Helper/FileStorageHelper.cs new file mode 100644 index 000000000..7f30af794 --- /dev/null +++ b/src/Spd.Presentation.Dynamics/Helper/FileStorageHelper.cs @@ -0,0 +1,45 @@ +using Spd.Utilities.FileStorage; +using Spd.Utilities.Shared.Exceptions; +using System.Net; + +namespace Spd.Presentation.Dynamics.Helper; + +public class FileStorageHelper +{ + public static Tag[] GetTagsFromStr(string? tagStr, string classification) + { + try + { + List taglist = new() { new Tag(SpdHeaderNames.HEADER_FILE_CLASSIFICATION, classification) }; + + if (!string.IsNullOrWhiteSpace(tagStr)) + { + string[] tags = tagStr.Split(','); + foreach (string tag in tags) + { + string[] strs = tag.Split('='); + if (strs.Length != 2) throw new OutOfRangeException(HttpStatusCode.BadRequest, $"Invalid {SpdHeaderNames.HEADER_FILE_TAG} string"); + taglist.Add( + new Tag(strs[0], strs[1]) + ); + } + } + return taglist.ToArray(); + } + catch + { + throw new ApiException(HttpStatusCode.BadRequest, $"Invalid {SpdHeaderNames.HEADER_FILE_TAG} string"); + } + } + + public static string GetStrFromTags(IEnumerable tags) + { + List tagStrlist = new(); + foreach (Tag t in tags) + { + if (t.Key != SpdHeaderNames.HEADER_FILE_CLASSIFICATION) + tagStrlist.Add($"{t.Key}={t.Value}"); + } + return string.Join(",", tagStrlist); + } +} diff --git a/src/Spd.Presentation.Dynamics/Models/CopyFileRequest.cs b/src/Spd.Presentation.Dynamics/Models/CopyFileRequest.cs new file mode 100644 index 000000000..c9f899326 --- /dev/null +++ b/src/Spd.Presentation.Dynamics/Models/CopyFileRequest.cs @@ -0,0 +1,8 @@ +namespace Spd.Presentation.Dynamics.Models +{ + public class CopyFileRequest + { + public string SourceKey { get; set; } + public string DestKey { get; set; } + } +} diff --git a/src/Spd.Presentation.Dynamics/Program.cs b/src/Spd.Presentation.Dynamics/Program.cs index a0d780092..45b2b71f8 100644 --- a/src/Spd.Presentation.Dynamics/Program.cs +++ b/src/Spd.Presentation.Dynamics/Program.cs @@ -13,11 +13,6 @@ var builder = WebApplication.CreateBuilder(args); -//todo: remove,just testing -ILogger logger = builder.Services.BuildServiceProvider().GetRequiredService>(); -logger.LogInformation("This is a testlog"); -//end - var assemblies = Directory.GetFiles(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? string.Empty, "*.dll", SearchOption.TopDirectoryOnly) .Where(assembly => { diff --git a/src/Spd.Resource.Applicants/Document/Contract.cs b/src/Spd.Resource.Applicants/Document/Contract.cs index 36691f5d1..135732e29 100644 --- a/src/Spd.Resource.Applicants/Document/Contract.cs +++ b/src/Spd.Resource.Applicants/Document/Contract.cs @@ -52,6 +52,8 @@ public record CreateStreamDocumentCmd : CreateDocumentCmd public record RemoveDocumentCmd(Guid DocumentUrlId) : DocumentCmd; public record ReactivateDocumentCmd(Guid DocumentUrlId) : DocumentCmd; public record UpdateDocumentCmd(Guid DocumentUrlId, DateOnly? ExpiryDate = null, DocumentTypeEnum? Tag1 = null, DocumentTypeEnum? Tag2 = null) : DocumentCmd; + //copy the sourceDocument to the new application + public record CopyDocumentCmd(Guid SourceDocumentUrlId, Guid DestApplicationId, Guid? SubmittedByApplicantId) : DocumentCmd; public enum DocumentTypeEnum { AdditionalGovIdDocument, diff --git a/src/Spd.Resource.Applicants/Document/DocumentRepository.cs b/src/Spd.Resource.Applicants/Document/DocumentRepository.cs index ec839b3f1..06874ffec 100644 --- a/src/Spd.Resource.Applicants/Document/DocumentRepository.cs +++ b/src/Spd.Resource.Applicants/Document/DocumentRepository.cs @@ -63,6 +63,7 @@ public async Task ManageAsync(DocumentCmd cmd, CancellationToken c RemoveDocumentCmd c => await DocumentRemoveAsync(c, ct), ReactivateDocumentCmd c => await DocumentReactivateAsync(c, ct), UpdateDocumentCmd c => await DocumentUpdateAsync(c, ct), + CopyDocumentCmd c => await DocumentCopyAsync(c, ct), _ => throw new NotSupportedException($"{cmd.GetType().Name} is not supported") }; } @@ -110,6 +111,59 @@ private async Task DocumentCreateAsync(CreateDocumentCmd cmd, Canc return _mapper.Map(documenturl); } + private async Task DocumentCopyAsync(CopyDocumentCmd cmd, CancellationToken ct) + { + spd_application? application = await _context.GetApplicationById(cmd.DestApplicationId, ct); + if (application == null) + throw new ArgumentException("invalid application id"); + + bcgov_documenturl sourceDoc = _context.bcgov_documenturls.Where(d => d.bcgov_documenturlid == cmd.SourceDocumentUrlId).FirstOrDefault(); + if (sourceDoc == null) + throw new ArgumentException("cannot find the source documenturl for copying"); + bcgov_documenturl destDoc = new bcgov_documenturl(); + destDoc.bcgov_documenturlid = Guid.NewGuid(); + destDoc.bcgov_url = $"spd_application/{cmd.DestApplicationId}"; + destDoc.bcgov_filename = sourceDoc.bcgov_filename; + destDoc.bcgov_filesize = sourceDoc.bcgov_filesize; + destDoc.bcgov_origincode = (int)BcGovOriginCode.Web; + destDoc.bcgov_receiveddate = sourceDoc.bcgov_receiveddate; + destDoc.bcgov_fileextension = sourceDoc.bcgov_fileextension; + destDoc.spd_expirydate = sourceDoc.spd_expirydate; + _context.AddTobcgov_documenturls(destDoc); + _context.SetLink(destDoc, nameof(destDoc.spd_ApplicationId), application); + if (sourceDoc._bcgov_tag1id_value != null) + { + var tag1 = _context.bcgov_tags.Where(t => t.bcgov_tagid == sourceDoc._bcgov_tag1id_value).FirstOrDefault(); + _context.SetLink(destDoc, nameof(destDoc.bcgov_Tag1Id), tag1); + } + if (sourceDoc._bcgov_tag2id_value != null) + { + var tag2 = _context.bcgov_tags.Where(t => t.bcgov_tagid == sourceDoc._bcgov_tag2id_value).FirstOrDefault(); + _context.SetLink(destDoc, nameof(destDoc.bcgov_Tag2Id), tag2); + } + if (sourceDoc._bcgov_tag3id_value != null) + { + var tag3 = _context.bcgov_tags.Where(t => t.bcgov_tagid == sourceDoc._bcgov_tag3id_value).FirstOrDefault(); + _context.SetLink(destDoc, nameof(destDoc.bcgov_Tag3Id), tag3); + } + if (cmd.SubmittedByApplicantId != null) + { + contact? contact = await _context.GetContactById((Guid)cmd.SubmittedByApplicantId, ct); + _context.SetLink(destDoc, nameof(destDoc.spd_SubmittedById), contact); + } + + await _fileStorageService.HandleCommand(new CopyFileCommand( + SourceKey: cmd.SourceDocumentUrlId.ToString(), + SourceFolder: $"spd_application/{sourceDoc._spd_applicationid_value}", + DestKey: destDoc.bcgov_documenturlid.ToString(), + DestFolder: $"spd_application/{cmd.DestApplicationId}" + ), ct); + + await _context.SaveChangesAsync(ct); + destDoc._spd_applicationid_value = application.spd_applicationid; + return _mapper.Map(destDoc); + } + private async Task DocumentRemoveAsync(RemoveDocumentCmd cmd, CancellationToken ct) { bcgov_documenturl? documenturl = _context.bcgov_documenturls.Where(d => d.bcgov_documenturlid == cmd.DocumentUrlId).FirstOrDefault(); @@ -234,6 +288,7 @@ await _fileStorageService.HandleCommand(new UpdateTagsCommand( ), ct); } + } diff --git a/src/Spd.Utilities.FileStorage/Contract.cs b/src/Spd.Utilities.FileStorage/Contract.cs index bb04bdb5e..dbf27948d 100644 --- a/src/Spd.Utilities.FileStorage/Contract.cs +++ b/src/Spd.Utilities.FileStorage/Contract.cs @@ -6,13 +6,21 @@ public interface IFileStorageService Task HandleQuery(StorageQuery query, CancellationToken cancellationToken); } + public interface ITransientFileStorageService + { + Task HandleCommand(StorageCommand cmd, CancellationToken cancellationToken); + Task HandleQuery(StorageQuery query, CancellationToken cancellationToken); + } + public abstract record StorageCommand(string Key, string? Folder); public record UploadFileCommand(string Key, string? Folder, File File, FileTag FileTag) : StorageCommand(Key, Folder); public record UploadFileStreamCommand(string Key, string? Folder, FileStream FileStream, FileTag FileTag) : StorageCommand(Key, Folder); - public record UpdateTagsCommand(string Key, string? Folder, FileTag? FileTag) : StorageCommand(Key, Folder); + //copy the file from source to dest for the same bucket. + public record CopyFileCommand(string SourceKey, string? SourceFolder, string DestKey, string? DestFolder) : StorageCommand(SourceKey, SourceFolder); + public record FileTag { public IEnumerable Tags { get; set; } = Array.Empty(); diff --git a/src/Spd.Utilities.FileStorage/FileStorageService.cs b/src/Spd.Utilities.FileStorage/FileStorageService.cs index 68082f5f9..86e465041 100644 --- a/src/Spd.Utilities.FileStorage/FileStorageService.cs +++ b/src/Spd.Utilities.FileStorage/FileStorageService.cs @@ -6,7 +6,7 @@ namespace Spd.Utilities.FileStorage { - internal class FileStorageService : IFileStorageService + internal class FileStorageService : IFileStorageService, ITransientFileStorageService { private readonly AmazonS3Client _amazonS3Client; private readonly IOptions _config; @@ -22,6 +22,7 @@ public async Task HandleCommand(StorageCommand cmd, CancellationToken ca UploadFileCommand c => await UploadStorageItem(c, cancellationToken), UploadFileStreamCommand c => await UploadStorageItemStream(c, cancellationToken), UpdateTagsCommand c => await UpdateTags(c, cancellationToken), + CopyFileCommand c => await CopyStorageItem(c, cancellationToken), _ => throw new NotSupportedException($"{cmd.GetType().Name} is not supported") }; } @@ -110,6 +111,26 @@ private async Task UpdateTags(UpdateTagsCommand cmd, CancellationToken c return cmd.Key; } + private async Task CopyStorageItem(CopyFileCommand cmd, CancellationToken cancellationToken) + { + var folder = cmd.Folder == null ? "" : $"{cmd.Folder}/"; + var key = $"{folder}{cmd.Key}"; + + var destFolder = cmd.DestFolder == null ? "" : $"{cmd.DestFolder}/"; + var destKey = $"{destFolder}/{cmd.DestKey}"; + var request = new CopyObjectRequest + { + SourceBucket = this._config.Value.Bucket, + SourceKey = key, + DestinationBucket = this._config.Value.Bucket, + DestinationKey = destKey, + }; + + var response = await _amazonS3Client.CopyObjectAsync(request, cancellationToken); + response.EnsureSuccess(); + + return cmd.Key; + } private async Task DownloadStorageItem(string key, string? folder, CancellationToken ct) { var dir = folder == null ? "" : $"{folder}/"; diff --git a/src/Spd.Utilities.FileStorage/S3Settings.cs b/src/Spd.Utilities.FileStorage/S3Settings.cs index 3146ef6ca..72c816696 100644 --- a/src/Spd.Utilities.FileStorage/S3Settings.cs +++ b/src/Spd.Utilities.FileStorage/S3Settings.cs @@ -1,5 +1,10 @@ namespace Spd.Utilities.FileStorage { + internal record StorageSetting + { + public S3Settings MainBucketSettings { get; set; } + public S3Settings TransientBucketSettings { get; set; } + } internal record S3Settings { public string AccessKey { get; set; } = null!; diff --git a/src/Spd.Utilities.FileStorage/ServiceExtensions.cs b/src/Spd.Utilities.FileStorage/ServiceExtensions.cs index 9173160a9..6e46c96cf 100644 --- a/src/Spd.Utilities.FileStorage/ServiceExtensions.cs +++ b/src/Spd.Utilities.FileStorage/ServiceExtensions.cs @@ -2,6 +2,7 @@ using Amazon.S3; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; namespace Spd.Utilities.FileStorage { @@ -9,23 +10,41 @@ public static class ServiceExtensions { public static IServiceCollection AddFileStorageProxy(this IServiceCollection services, IConfiguration configuration) { - var options = configuration.GetSection("storage:S3").Get()!; + var options = configuration.GetSection("storage").Get()!; - services.Configure(opts => configuration.GetSection("storage:S3").Bind(opts)); - - var config = new AmazonS3Config + services.Configure(opts => configuration.GetSection("storage").Bind(opts)); + + //create main bucket + var mainBucketConfig = new AmazonS3Config { - ServiceURL = options.Url.ToString(), + ServiceURL = options.MainBucketSettings.Url.ToString(), ForcePathStyle = true, SignatureVersion = "2", SignatureMethod = SigningAlgorithm.HmacSHA1, UseHttp = false, }; - var client = new AmazonS3Client(new BasicAWSCredentials(options.AccessKey, options.Secret), config); + var mainClient = new AmazonS3Client(new BasicAWSCredentials(options.MainBucketSettings.AccessKey, options.MainBucketSettings.Secret), mainBucketConfig); + services.AddSingleton(sp => new FileStorageService( + mainClient, + Options.Create(options.MainBucketSettings))); - services.AddSingleton(client); - services.AddTransient(); + //create transient bucket + if (options.TransientBucketSettings?.Url != null && string.IsNullOrWhiteSpace(options.TransientBucketSettings?.Url.ToString())) + { + var transientBucketConfig = new AmazonS3Config + { + ServiceURL = options.TransientBucketSettings.Url.ToString(), + ForcePathStyle = true, + SignatureVersion = "2", + SignatureMethod = SigningAlgorithm.HmacSHA1, + UseHttp = false, + }; + var transientClient = new AmazonS3Client(new BasicAWSCredentials(options.TransientBucketSettings.AccessKey, options.TransientBucketSettings.Secret), mainBucketConfig); + services.AddSingleton(sp => new FileStorageService( + transientClient, + Options.Create(options.TransientBucketSettings))); + } return services; }