using Amazon.S3; using Amazon.S3.Model; using Amazon.S3.Transfer; using Marco.Pms.Model.Utilities; using MarcoBMS.Services.Service; using Microsoft.Extensions.Options; using MimeDetective; using System.Text.RegularExpressions; namespace Marco.Pms.Services.Service { public class S3UploadService { private readonly IAmazonS3 _s3Client; private readonly string _bucketName; private readonly ILoggingService _logger; private readonly IConfiguration _configuration; public S3UploadService(IOptions awsOptions, ILoggingService logger, IConfiguration configuration) { _logger = logger; _configuration = configuration; var settings = awsOptions.Value; var region = Amazon.RegionEndpoint.GetBySystemName(settings.Region); _bucketName = settings.BucketName; _s3Client = new AmazonS3Client(settings.AccessKey, settings.SecretKey, region); } //public async Task UploadFileAsync(string fileName, string contentType) public async Task UploadFileAsync(string base64, string fileType, string objectKey) { byte[] fileBytes; var allowedFilesType = _configuration.GetSection("WhiteList:ContentType") .GetChildren() .Select(x => x.Value) .ToList(); if (allowedFilesType == null || !allowedFilesType.Contains(fileType)) { _logger.LogWarning("Unsupported file type. {FileType}", fileType); //throw new InvalidOperationException("Unsupported file type."); return; } fileBytes = Convert.FromBase64String(base64); using var fileStream = new MemoryStream(fileBytes); var uploadRequest = new TransferUtilityUploadRequest { InputStream = fileStream, Key = objectKey, BucketName = _bucketName, ContentType = fileType, AutoCloseStream = true }; try { var transferUtility = new TransferUtility(_s3Client); await transferUtility.UploadAsync(uploadRequest); _logger.LogInfo("File uploaded to Amazon S3"); } catch (Exception ex) { _logger.LogError(ex, "Error ocurred while uploading file to S3", ex.Message); } } public string GeneratePreSignedUrl(string objectKey) { int expiresInMinutes = 10; var request = new GetPreSignedUrlRequest { BucketName = _bucketName, Key = objectKey, Expires = DateTime.UtcNow.AddMinutes(expiresInMinutes), Verb = HttpVerb.GET // for download }; try { string url = _s3Client.GetPreSignedURL(request); _logger.LogInfo("Requested presigned url from Amazon S3"); return url; } catch (Exception ex) { _logger.LogError(ex, "Error occured while requesting presigned url from Amazon S3"); return string.Empty; } } public async Task DeleteFileAsync(string objectKey) { try { var deleteRequest = new DeleteObjectRequest { BucketName = _bucketName, Key = objectKey }; var response = await _s3Client.DeleteObjectAsync(deleteRequest); _logger.LogInfo("File deleted from Amazon S3"); return response.HttpStatusCode == System.Net.HttpStatusCode.NoContent; } catch (Exception ex) { _logger.LogError(ex, "while deleting from Amazon S3"); return false; } } public string GenerateFileName(string contentType, Guid entityId, string? name) { string extenstion = GetExtensionFromMimeType(contentType); if (string.IsNullOrEmpty(name)) return $"{entityId}_{DateTime.UtcNow:yyyyMMddHHmmssfff}{extenstion}"; return $"{name}_{entityId}_{DateTime.UtcNow:yyyyMMddHHmmssfff}{extenstion}"; } public string GetExtensionFromMimeType(string contentType) { switch (contentType.ToLowerInvariant()) { case "application/pdf": return ".pdf"; case "application/msword": return ".doc"; case "application/vnd.openxmlformats-officedocument.wordprocessingml.document": return ".docx"; case "application/vnd.ms-excel": return ".xls"; case "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": return ".xlsx"; case "application/mspowerpoint": return ".ppt"; case "application/vnd.openxmlformats-officedocument.presentationml.presentation": return ".pptx"; case "text/plain": return ".txt"; case "application/rtf": return ".rtf"; case "image/jpeg": return ".jpg"; case "image/png": return ".png"; case "image/gif": return ".gif"; case "image/bmp": return ".bmp"; case "text/csv": return ".csv"; default: return ""; // or ".bin", or throw an error, based on your needs } } public string GetContentTypeFromBase64(string base64String) { if (string.IsNullOrEmpty(base64String)) { return string.Empty; // Or handle the empty case as needed } try { // 1. Decode the Base64 string to a byte array byte[] decodedBytes = Convert.FromBase64String(base64String); // 2. Create a ContentInspector (using default definitions for this example) var inspector = new ContentInspectorBuilder() { Definitions = MimeDetective.Definitions.DefaultDefinitions.All() }.Build(); // 3. Inspect the byte array to determine the content type var results = inspector.Inspect(decodedBytes); if (results.Any()) { var bestMatch = results .OrderByDescending(r => r.Points) .FirstOrDefault(); if (bestMatch?.Definition?.File?.MimeType != null) { return bestMatch.Definition.File.MimeType; } else { _logger.LogWarning("Warning: Could not find MimeType, Type, or ContentType property in Definition."); return "application/octet-stream"; } } else { return "application/octet-stream"; // Default if type cannot be determined } } catch (FormatException fEx) { // Handle cases where the input string is not valid Base64 _logger.LogError(fEx, "Invalid Base64 string."); return string.Empty; } catch (Exception ex) { // Handle other potential errors during decoding or inspection _logger.LogError(ex, "An error occurred while decoding base64"); return string.Empty; } } public bool IsBase64String(string? input) { if (string.IsNullOrWhiteSpace(input)) return false; // Normalize string input = input.Trim(); // Length must be multiple of 4 if (input.Length % 4 != 0) return false; // Valid Base64 characters with correct padding var base64Regex = new Regex(@"^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$"); if (!base64Regex.IsMatch(input)) return false; try { // Decode and re-encode to confirm validity var bytes = Convert.FromBase64String(input); var reEncoded = Convert.ToBase64String(bytes); return input == reEncoded; } catch { return false; } } } }