Implemented functionality to whitelist file extensions during upload to S3 #26

Merged
admin merged 2 commits from Ashutosh_Task#106_Upload_Whitelist_Only into Feature_Forum 2025-04-26 10:52:30 +00:00
13 changed files with 272 additions and 217 deletions

View File

@ -2,7 +2,6 @@
{ {
public class AddCommentDto public class AddCommentDto
{ {
public Guid Id { get; set; } = Guid.Empty;
public Guid TicketId { get; set; } = Guid.Empty; public Guid TicketId { get; set; } = Guid.Empty;
public Guid AuthorId { get; set; } public Guid AuthorId { get; set; }
public string MessageText { get; set; } = string.Empty; public string MessageText { get; set; } = string.Empty;

View File

@ -2,7 +2,6 @@
{ {
public class CreateTicketDto public class CreateTicketDto
{ {
public Guid Id { get; set; } = Guid.Empty;
public string Subject { get; set; } = string.Empty; public string Subject { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty; public string Description { get; set; } = string.Empty;
public Guid StatusId { get; set; } public Guid StatusId { get; set; }

View File

@ -2,7 +2,6 @@
{ {
public class ForumAttachmentDto public class ForumAttachmentDto
{ {
public Guid Id { get; set; } = Guid.Empty;
public Guid TicketId { get; set; } = Guid.Empty; public Guid TicketId { get; set; } = Guid.Empty;
public Guid? CommentId { get; set; } public Guid? CommentId { get; set; }
public string FileName { get; set; } = string.Empty; public string FileName { get; set; } = string.Empty;

View File

@ -0,0 +1,14 @@
namespace Marco.Pms.Model.Dtos.Forum
{
public class UpdateAttachmentDto
{
public Guid Id { get; set; }
public Guid TicketId { get; set; } = Guid.Empty;
public Guid? CommentId { get; set; }
public string FileName { get; set; } = string.Empty;
public string? Base64Data { get; set; }
public int FileSize { get; set; }
public string ContentType { get; set; } = string.Empty;
public DateTime SentAt { get; set; }
}
}

View File

@ -8,6 +8,6 @@
public string MessageText { get; set; } = string.Empty; public string MessageText { get; set; } = string.Empty;
public DateTime SentAt { get; set; } public DateTime SentAt { get; set; }
public Guid? ParentMessageId { get; set; } // For threaded replies public Guid? ParentMessageId { get; set; } // For threaded replies
public ICollection<ForumAttachmentDto>? Attachments { get; set; } public ICollection<UpdateAttachmentDto>? Attachments { get; set; }
} }
} }

View File

@ -11,7 +11,7 @@
public DateTime CreatedAt { get; set; } public DateTime CreatedAt { get; set; }
public int LinkedProjectId { get; set; } public int LinkedProjectId { get; set; }
public int? LinkedActivityId { get; set; } // task or project ID public int? LinkedActivityId { get; set; } // task or project ID
public ICollection<ForumAttachmentDto>? Attachments { get; set; } public ICollection<UpdateAttachmentDto>? Attachments { get; set; }
public Guid PriorityId { get; set; } public Guid PriorityId { get; set; }
public ICollection<Guid>? TagIds { get; set; } public ICollection<Guid>? TagIds { get; set; }
public int TenantId { get; set; } public int TenantId { get; set; }

View File

@ -77,6 +77,16 @@ namespace Marco.Pms.Model.Mapper
FileId = fileId, FileId = fileId,
}; };
} }
public static TicketAttachment ToTicketAttachmentFromUpdateAttachmentDto(this UpdateAttachmentDto AttachmentDto, Guid ticketId, Guid fileId, Guid? commentId = null)
{
return new TicketAttachment
{
TicketId = ticketId,
CommentId = commentId,
FileName = AttachmentDto.FileName,
FileId = fileId,
};
}
public static Document ToDocumentFromForumAttachmentDto(this ForumAttachmentDto AttachmentDto, string objectKey, string thumbS3Key, DateTime uploadedAt, int tenantId) public static Document ToDocumentFromForumAttachmentDto(this ForumAttachmentDto AttachmentDto, string objectKey, string thumbS3Key, DateTime uploadedAt, int tenantId)
{ {
@ -92,6 +102,20 @@ namespace Marco.Pms.Model.Mapper
TenantId = tenantId TenantId = tenantId
}; };
} }
public static Document ToDocumentFromUpdateAttachmentDto(this UpdateAttachmentDto AttachmentDto, string objectKey, string thumbS3Key, DateTime uploadedAt, int tenantId)
{
return new Document
{
FileName = AttachmentDto.FileName,
ContentType = AttachmentDto.ContentType,
S3Key = objectKey,
ThumbS3Key = thumbS3Key,
Base64Data = AttachmentDto.Base64Data,
FileSize = AttachmentDto.FileSize,
UploadedAt = uploadedAt,
TenantId = tenantId
};
}
public static ForumTicketVM ToForumTicketVMFromTicketForum(this TicketForum ticket, Employee employee) public static ForumTicketVM ToForumTicketVMFromTicketForum(this TicketForum ticket, Employee employee)
{ {
return new ForumTicketVM return new ForumTicketVM

View File

@ -57,7 +57,7 @@ namespace MarcoBMS.Services.Controllers
foreach (var attendanceLog in lstAttendance) foreach (var attendanceLog in lstAttendance)
{ {
string objectKey = attendanceLog.Document.S3Key; string objectKey = attendanceLog.Document.S3Key;
string preSignedUrl = await _s3Service.GeneratePreSignedUrlAsync(objectKey); string preSignedUrl = _s3Service.GeneratePreSignedUrlAsync(objectKey);
attendanceLogVMs.Add(attendanceLog.ToAttendanceLogVMFromAttendanceLog(preSignedUrl, preSignedUrl)); attendanceLogVMs.Add(attendanceLog.ToAttendanceLogVMFromAttendanceLog(preSignedUrl, preSignedUrl));
} }
return Ok(ApiResponse<object>.SuccessResponse(attendanceLogVMs, System.String.Format("{0} Attendance records fetched successfully", lstAttendance.Count), 200)); return Ok(ApiResponse<object>.SuccessResponse(attendanceLogVMs, System.String.Format("{0} Attendance records fetched successfully", lstAttendance.Count), 200));
@ -509,33 +509,16 @@ namespace MarcoBMS.Services.Controllers
if (Image != null && Image.ContentType != null) if (Image != null && Image.ContentType != null)
{ {
byte[] fileBytes;
if (string.IsNullOrEmpty(Image.Base64Data)) if (string.IsNullOrEmpty(Image.Base64Data))
return BadRequest(ApiResponse<object>.ErrorResponse("Base64 data is missing", "Base64 data is missing", 400)); return BadRequest(ApiResponse<object>.ErrorResponse("Base64 data is missing", "Base64 data is missing", 400));
try
{
//If base64 has a data URI prefix, strip it
var base64 = Image.Base64Data.Contains(",")
? Image.Base64Data.Substring(Image.Base64Data.IndexOf(",") + 1)
: Image.Base64Data;
fileBytes = Convert.FromBase64String(base64); objectKey = await _s3Service.UploadFileAsync(Image.Base64Data, TenantId, "attendance");
} preSignedUrl = _s3Service.GeneratePreSignedUrlAsync(objectKey);
catch (Exception ex)
{
return BadRequest(ApiResponse<object>.ErrorResponse(ex.Message, ex, 400)); ;
}
using var stream = new MemoryStream(fileBytes);
string fileName = _s3Service.GenerateFileName(Image.ContentType, TenantId, "Attendance");
objectKey = await _s3Service.UploadFileAsync(stream, fileName, Image.ContentType);
preSignedUrl = await _s3Service.GeneratePreSignedUrlAsync(objectKey);
document = new Document document = new Document
{ {
FileName = Image.FileName ?? fileName, FileName = Image.FileName ?? "",
ContentType = Image.ContentType, ContentType = Image.ContentType,
S3Key = objectKey, S3Key = objectKey,
Base64Data = Image.Base64Data, Base64Data = Image.Base64Data,

View File

@ -62,34 +62,14 @@ namespace Marco.Pms.Services.Controllers
{ {
foreach (var attachmentDto in createTicketDto.Attachments) foreach (var attachmentDto in createTicketDto.Attachments)
{ {
byte[] fileBytes;
var Image = attachmentDto; var Image = attachmentDto;
if (string.IsNullOrEmpty(Image.Base64Data)) if (string.IsNullOrEmpty(Image.Base64Data))
{ {
_logger.LogError("Base64 data is missing"); _logger.LogError("Base64 data is missing");
return BadRequest(ApiResponse<object>.ErrorResponse("Base64 data is missing", "Base64 data is missing", 400)); return BadRequest(ApiResponse<object>.ErrorResponse("Base64 data is missing", "Base64 data is missing", 400));
} }
try
{
//If base64 has a data URI prefix, strip it
var base64 = Image.Base64Data.Contains(",")
? Image.Base64Data.Substring(Image.Base64Data.IndexOf(",") + 1)
: Image.Base64Data;
fileBytes = Convert.FromBase64String(base64); var objectKey = await _s3Service.UploadFileAsync(Image.Base64Data, tenantId, "forum");
}
catch (Exception ex)
{
_logger.LogError("{error}", ex.Message);
return BadRequest(ApiResponse<object>.ErrorResponse(ex.Message, ex, 400)); ;
}
using var stream = new MemoryStream(fileBytes);
string fileName = _s3Service.GenerateFileName(Image.ContentType, tenantId, string.Empty);
var objectKey = await _s3Service.UploadFileAsync(stream, fileName, Image.ContentType);
Document document = attachmentDto.ToDocumentFromForumAttachmentDto(objectKey, objectKey, createTicketDto.CreatedAt, tenantId); Document document = attachmentDto.ToDocumentFromForumAttachmentDto(objectKey, objectKey, createTicketDto.CreatedAt, tenantId);
_context.Documents.Add(document); _context.Documents.Add(document);
@ -139,7 +119,7 @@ namespace Marco.Pms.Services.Controllers
string preSignedUrl = string.Empty; string preSignedUrl = string.Empty;
if (document != null) if (document != null)
{ {
preSignedUrl = await _s3Service.GeneratePreSignedUrlAsync(document.S3Key); preSignedUrl = _s3Service.GeneratePreSignedUrlAsync(document.S3Key);
} }
attachmentVMs.Add(attachment.ToTicketAttachmentVMFromTicketAttachment(preSignedUrl, preSignedUrl)); attachmentVMs.Add(attachment.ToTicketAttachmentVMFromTicketAttachment(preSignedUrl, preSignedUrl));
} }
@ -198,40 +178,20 @@ namespace Marco.Pms.Services.Controllers
{ {
if (!existingattachmentids.Contains(attachmentDto.Id) && attachmentDto.TicketId != updateTicketDto.Id) if (!existingattachmentids.Contains(attachmentDto.Id) && attachmentDto.TicketId != updateTicketDto.Id)
{ {
byte[] fileBytes;
var Image = attachmentDto; var Image = attachmentDto;
if (string.IsNullOrEmpty(Image.Base64Data)) if (string.IsNullOrEmpty(Image.Base64Data))
{ {
_logger.LogError("Base64 data is missing"); _logger.LogError("Base64 data is missing");
return BadRequest(ApiResponse<object>.ErrorResponse("Base64 data is missing", "Base64 data is missing", 400)); return BadRequest(ApiResponse<object>.ErrorResponse("Base64 data is missing", "Base64 data is missing", 400));
} }
try
{
//If base64 has a data URI prefix, strip it
var base64 = Image.Base64Data.Contains(",")
? Image.Base64Data.Substring(Image.Base64Data.IndexOf(",") + 1)
: Image.Base64Data;
fileBytes = Convert.FromBase64String(base64); var objectKey = await _s3Service.UploadFileAsync(Image.Base64Data, tenantId, "forum");
}
catch (Exception ex)
{
_logger.LogError("{error}", ex.Message);
return BadRequest(ApiResponse<object>.ErrorResponse(ex.Message, ex, 400)); ;
}
using var stream = new MemoryStream(fileBytes); Document document = attachmentDto.ToDocumentFromUpdateAttachmentDto(objectKey, objectKey, updateTicketDto.CreatedAt, tenantId);
string fileName = _s3Service.GenerateFileName(Image.ContentType, tenantId, string.Empty);
var objectKey = await _s3Service.UploadFileAsync(stream, fileName, Image.ContentType);
Document document = attachmentDto.ToDocumentFromForumAttachmentDto(objectKey, objectKey, updateTicketDto.CreatedAt, tenantId);
_context.Documents.Add(document); _context.Documents.Add(document);
await _context.SaveChangesAsync(); await _context.SaveChangesAsync();
var attachment = attachmentDto.ToTicketAttachmentFromForumAttachmentDto(ticketForum.Id, document.Id); var attachment = attachmentDto.ToTicketAttachmentFromUpdateAttachmentDto(ticketForum.Id, document.Id);
attachments.Add(attachment); attachments.Add(attachment);
} }
} }
@ -313,7 +273,7 @@ namespace Marco.Pms.Services.Controllers
string preSignedUrl = string.Empty; string preSignedUrl = string.Empty;
if (document != null) if (document != null)
{ {
preSignedUrl = await _s3Service.GeneratePreSignedUrlAsync(document.S3Key); preSignedUrl = _s3Service.GeneratePreSignedUrlAsync(document.S3Key);
} }
if (attachment.CommentId == null) if (attachment.CommentId == null)
{ {
@ -376,33 +336,14 @@ namespace Marco.Pms.Services.Controllers
{ {
foreach (var attachmentDto in addCommentDto.Attachments) foreach (var attachmentDto in addCommentDto.Attachments)
{ {
byte[] fileBytes;
var Image = attachmentDto; var Image = attachmentDto;
if (string.IsNullOrEmpty(Image.Base64Data)) if (string.IsNullOrEmpty(Image.Base64Data))
{ {
_logger.LogError("Base64 data is missing"); _logger.LogError("Base64 data is missing");
return BadRequest(ApiResponse<object>.ErrorResponse("Base64 data is missing", "Base64 data is missing", 400)); return BadRequest(ApiResponse<object>.ErrorResponse("Base64 data is missing", "Base64 data is missing", 400));
} }
try
{
//If base64 has a data URI prefix, strip it
var base64 = Image.Base64Data.Contains(",")
? Image.Base64Data.Substring(Image.Base64Data.IndexOf(",") + 1)
: Image.Base64Data;
fileBytes = Convert.FromBase64String(base64); var objectKey = await _s3Service.UploadFileAsync(Image.Base64Data, tenantId, "forum");
}
catch (Exception ex)
{
_logger.LogError("{error}", ex.Message);
return BadRequest(ApiResponse<object>.ErrorResponse(ex.Message, ex, 400)); ;
}
using var stream = new MemoryStream(fileBytes);
string fileName = _s3Service.GenerateFileName(Image.ContentType, tenantId, string.Empty);
var objectKey = await _s3Service.UploadFileAsync(stream, fileName, Image.ContentType);
Document document = attachmentDto.ToDocumentFromForumAttachmentDto(objectKey, objectKey, addCommentDto.SentAt, tenantId); Document document = attachmentDto.ToDocumentFromForumAttachmentDto(objectKey, objectKey, addCommentDto.SentAt, tenantId);
_context.Documents.Add(document); _context.Documents.Add(document);
@ -428,7 +369,7 @@ namespace Marco.Pms.Services.Controllers
string preSignedUrl = string.Empty; string preSignedUrl = string.Empty;
if (document != null) if (document != null)
{ {
preSignedUrl = await _s3Service.GeneratePreSignedUrlAsync(document.S3Key); preSignedUrl = _s3Service.GeneratePreSignedUrlAsync(document.S3Key);
} }
attachmentVMs.Add(attachment.ToTicketAttachmentVMFromTicketAttachment(preSignedUrl, preSignedUrl)); attachmentVMs.Add(attachment.ToTicketAttachmentVMFromTicketAttachment(preSignedUrl, preSignedUrl));
} }
@ -461,48 +402,33 @@ namespace Marco.Pms.Services.Controllers
List<TicketAttachment> existingAttachments = await _context.TicketAttachments.Where(a => a.CommentId == updateComment.Id).ToListAsync(); List<TicketAttachment> existingAttachments = await _context.TicketAttachments.Where(a => a.CommentId == updateComment.Id).ToListAsync();
var existingattachmentids = existingAttachments.Select(a => a.Id).ToList(); var existingattachmentids = existingAttachments.Select(a => a.Id).ToList();
var attachmentDtoids = updateCommentDto.Attachments.Select(a => a.Id).ToList(); List<Guid> attachmentDtoids = new List<Guid>();
if (updateCommentDto.Attachments != null)
{
attachmentDtoids = updateCommentDto.Attachments.Select(a => a.Id).ToList();
foreach (var attachmentDto in updateCommentDto.Attachments) foreach (var attachmentDto in updateCommentDto.Attachments)
{ {
if (!existingattachmentids.Contains(attachmentDto.Id) && attachmentDto.CommentId != updateComment.Id) if (!existingattachmentids.Contains(attachmentDto.Id) && attachmentDto.CommentId != updateComment.Id)
{ {
byte[] fileBytes;
var Image = attachmentDto; var Image = attachmentDto;
if (string.IsNullOrEmpty(Image.Base64Data)) if (string.IsNullOrEmpty(Image.Base64Data))
{ {
_logger.LogError("Base64 data is missing"); _logger.LogError("Base64 data is missing");
return BadRequest(ApiResponse<object>.ErrorResponse("Base64 data is missing", "Base64 data is missing", 400)); return BadRequest(ApiResponse<object>.ErrorResponse("Base64 data is missing", "Base64 data is missing", 400));
} }
try
{
//If base64 has a data URI prefix, strip it
var base64 = Image.Base64Data.Contains(",")
? Image.Base64Data.Substring(Image.Base64Data.IndexOf(",") + 1)
: Image.Base64Data;
fileBytes = Convert.FromBase64String(base64); var objectKey = await _s3Service.UploadFileAsync(Image.Base64Data, tenantId, "forum");
}
catch (Exception ex)
{
_logger.LogError("{error}", ex.Message);
return BadRequest(ApiResponse<object>.ErrorResponse(ex.Message, ex, 400)); ;
}
using var stream = new MemoryStream(fileBytes);
Document document = attachmentDto.ToDocumentFromUpdateAttachmentDto(objectKey, objectKey, existingComment.SentAt, tenantId);
string fileName = _s3Service.GenerateFileName(Image.ContentType, tenantId, string.Empty);
var objectKey = await _s3Service.UploadFileAsync(stream, fileName, Image.ContentType);
Document document = attachmentDto.ToDocumentFromForumAttachmentDto(objectKey, objectKey, existingComment.SentAt, tenantId);
_context.Documents.Add(document); _context.Documents.Add(document);
await _context.SaveChangesAsync(); await _context.SaveChangesAsync();
var attachment = attachmentDto.ToTicketAttachmentFromForumAttachmentDto(existingComment.TicketId, document.Id, updateComment.Id); var attachment = attachmentDto.ToTicketAttachmentFromUpdateAttachmentDto(existingComment.TicketId, document.Id, updateComment.Id);
attachments.Add(attachment); attachments.Add(attachment);
} }
} }
}
if (attachments.Count != 0) if (attachments.Count != 0)
{ {
_context.TicketAttachments.AddRange(attachments); _context.TicketAttachments.AddRange(attachments);
@ -537,7 +463,7 @@ namespace Marco.Pms.Services.Controllers
string preSignedUrl = string.Empty; string preSignedUrl = string.Empty;
if (document != null) if (document != null)
{ {
preSignedUrl = await _s3Service.GeneratePreSignedUrlAsync(document.S3Key); preSignedUrl = _s3Service.GeneratePreSignedUrlAsync(document.S3Key);
} }
attachmentVMs.Add(attachment.ToTicketAttachmentVMFromTicketAttachment(preSignedUrl, preSignedUrl)); attachmentVMs.Add(attachment.ToTicketAttachmentVMFromTicketAttachment(preSignedUrl, preSignedUrl));
} }
@ -568,32 +494,13 @@ namespace Marco.Pms.Services.Controllers
foreach (var forumAttachmentDto in forumAttachmentDtos) foreach (var forumAttachmentDto in forumAttachmentDtos)
{ {
byte[] fileBytes;
if (string.IsNullOrEmpty(forumAttachmentDto.Base64Data)) if (string.IsNullOrEmpty(forumAttachmentDto.Base64Data))
{ {
_logger.LogError("Base64 data is missing"); _logger.LogError("Base64 data is missing");
return BadRequest(ApiResponse<object>.ErrorResponse("Base64 data is missing", "Base64 data is missing", 400)); return BadRequest(ApiResponse<object>.ErrorResponse("Base64 data is missing", "Base64 data is missing", 400));
} }
try
{
//If base64 has a data URI prefix, strip it
var base64 = forumAttachmentDto.Base64Data.Contains(",")
? forumAttachmentDto.Base64Data.Substring(forumAttachmentDto.Base64Data.IndexOf(",") + 1)
: forumAttachmentDto.Base64Data;
fileBytes = Convert.FromBase64String(base64); var objectKey = await _s3Service.UploadFileAsync(forumAttachmentDto.Base64Data, tenantId, "forum");
}
catch (Exception ex)
{
_logger.LogError("{error}", ex.Message);
return BadRequest(ApiResponse<object>.ErrorResponse(ex.Message, ex, 400)); ;
}
using var stream = new MemoryStream(fileBytes);
string fileName = _s3Service.GenerateFileName(forumAttachmentDto.ContentType, tenantId, string.Empty);
var objectKey = await _s3Service.UploadFileAsync(stream, fileName, forumAttachmentDto.ContentType);
Document document = forumAttachmentDto.ToDocumentFromForumAttachmentDto(objectKey, objectKey, forumAttachmentDto.SentAt, tenantId); Document document = forumAttachmentDto.ToDocumentFromForumAttachmentDto(objectKey, objectKey, forumAttachmentDto.SentAt, tenantId);
_context.Documents.Add(document); _context.Documents.Add(document);
@ -603,7 +510,7 @@ namespace Marco.Pms.Services.Controllers
_context.TicketAttachments.Add(attachment); _context.TicketAttachments.Add(attachment);
await _context.SaveChangesAsync(); await _context.SaveChangesAsync();
string preSignedUrl = await _s3Service.GeneratePreSignedUrlAsync(document.S3Key); string preSignedUrl = _s3Service.GeneratePreSignedUrlAsync(document.S3Key);
TicketAttachmentVM attachmentVM = attachment.ToTicketAttachmentVMFromTicketAttachment(preSignedUrl, preSignedUrl); TicketAttachmentVM attachmentVM = attachment.ToTicketAttachmentVMFromTicketAttachment(preSignedUrl, preSignedUrl);
ticketAttachmentVMs.Add(attachmentVM); ticketAttachmentVMs.Add(attachmentVM);
@ -668,7 +575,7 @@ namespace Marco.Pms.Services.Controllers
string preSignedUrl = string.Empty; string preSignedUrl = string.Empty;
if (document != null) if (document != null)
{ {
preSignedUrl = await _s3Service.GeneratePreSignedUrlAsync(document.S3Key); preSignedUrl = _s3Service.GeneratePreSignedUrlAsync(document.S3Key);
} }
if (attachment.CommentId == null) if (attachment.CommentId == null)
{ {
@ -743,7 +650,7 @@ namespace Marco.Pms.Services.Controllers
string preSignedUrl = string.Empty; string preSignedUrl = string.Empty;
if (document != null) if (document != null)
{ {
preSignedUrl = await _s3Service.GeneratePreSignedUrlAsync(document.S3Key); preSignedUrl = _s3Service.GeneratePreSignedUrlAsync(document.S3Key);
} }
if (attachment.CommentId == null) if (attachment.CommentId == null)
{ {
@ -842,7 +749,7 @@ namespace Marco.Pms.Services.Controllers
string preSignedUrl = string.Empty; string preSignedUrl = string.Empty;
if (document != null) if (document != null)
{ {
preSignedUrl = await _s3Service.GeneratePreSignedUrlAsync(document.S3Key); preSignedUrl = _s3Service.GeneratePreSignedUrlAsync(document.S3Key);
} }
if (attachment.CommentId == null) if (attachment.CommentId == null)
{ {

View File

@ -1,4 +1,3 @@
using Marco.Pms.Model.Utilities;
using Marco.Pms.Services.Service; using Marco.Pms.Services.Service;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
@ -33,41 +32,22 @@ namespace MarcoBMS.Services.Controllers
}) })
.ToArray(); .ToArray();
} }
[HttpPost("upload-image")] //[HttpPost("upload-image")]
public async Task<IActionResult> SendToSThree([FromBody] FileUploadModel Image) //public async Task<IActionResult> SendToSThree([FromBody] FileUploadModel Image)
{ //{
if (string.IsNullOrEmpty(Image.Base64Data)) // if (string.IsNullOrEmpty(Image.Base64Data))
return BadRequest("Base64 data is missing"); // return BadRequest("Base64 data is missing");
// var objectKey = await _s3Service.UploadFileAsync(Image.Base64Data, 1, "Forum");
// //var objectKey = await _s3Service.UploadFileAsync(Image.FileName, Image.ContentType);
// var preSignedUrl = _s3Service.GeneratePreSignedUrlAsync(objectKey);
byte[] fileBytes; // return Ok(new
// {
// objectKey,
// url = preSignedUrl
// });
try //}
{
//If base64 has a data URI prefix, strip it
var base64 = Image.Base64Data.Contains(",")
? Image.Base64Data.Substring(Image.Base64Data.IndexOf(",") + 1)
: Image.Base64Data;
fileBytes = Convert.FromBase64String(base64);
}
catch (Exception error)
{
//return BadRequest("Invalid base64 string.");
return BadRequest(error);
}
using var stream = new MemoryStream(fileBytes);
var objectKey = await _s3Service.UploadFileAsync(stream, Image.FileName, Image.ContentType);
//var objectKey = await _s3Service.UploadFileAsync(Image.FileName, Image.ContentType);
var preSignedUrl = await _s3Service.GeneratePreSignedUrlAsync(objectKey);
return Ok(new
{
objectKey,
url = preSignedUrl
});
}
} }
} }

View File

@ -28,6 +28,8 @@
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.21.0" /> <PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.21.0" />
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="9.0.0" /> <PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="9.0.0" />
<PackageReference Include="Mime-Detective" Version="24.12.2" />
<PackageReference Include="Mime-Detective.Definitions.Exhaustive" Version="24.12.2" />
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="8.0.2" /> <PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="8.0.2" />
<PackageReference Include="Serilog.AspNetCore" Version="9.0.0" /> <PackageReference Include="Serilog.AspNetCore" Version="9.0.0" />
<PackageReference Include="Serilog.Sinks.Async" Version="2.1.0" /> <PackageReference Include="Serilog.Sinks.Async" Version="2.1.0" />

View File

@ -4,6 +4,7 @@ using Amazon.S3.Transfer;
using Marco.Pms.Model.Utilities; using Marco.Pms.Model.Utilities;
using MarcoBMS.Services.Service; using MarcoBMS.Services.Service;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using MimeDetective;
namespace Marco.Pms.Services.Service namespace Marco.Pms.Services.Service
{ {
@ -13,10 +14,12 @@ namespace Marco.Pms.Services.Service
private readonly IAmazonS3 _s3Client; private readonly IAmazonS3 _s3Client;
private readonly string _bucketName = "your-bucket-name"; private readonly string _bucketName = "your-bucket-name";
private readonly ILoggingService _logger; private readonly ILoggingService _logger;
private readonly IConfiguration _configuration;
public S3UploadService(IOptions<AWSSettings> awsOptions, ILoggingService logger) public S3UploadService(IOptions<AWSSettings> awsOptions, ILoggingService logger, IConfiguration configuration)
{ {
_logger = logger; _logger = logger;
_configuration = configuration;
var settings = awsOptions.Value; var settings = awsOptions.Value;
var region = Amazon.RegionEndpoint.GetBySystemName(settings.Region); var region = Amazon.RegionEndpoint.GetBySystemName(settings.Region);
@ -25,8 +28,28 @@ namespace Marco.Pms.Services.Service
_s3Client = new AmazonS3Client(settings.AccessKey, settings.SecretKey, region); _s3Client = new AmazonS3Client(settings.AccessKey, settings.SecretKey, region);
} }
//public async Task<string> UploadFileAsync(string fileName, string contentType) //public async Task<string> UploadFileAsync(string fileName, string contentType)
public async Task<string> UploadFileAsync(Stream fileStream, string fileName, string contentType) public async Task<string> UploadFileAsync(string base64Data, int tenantId, string tag)
{ {
byte[] fileBytes;
//If base64 has a data URI prefix, strip it
var base64 = base64Data.Contains(",")
? base64Data.Substring(base64Data.IndexOf(",") + 1)
: base64Data;
var allowedFilesType = _configuration.GetSection("WhiteList:ContentType")
.GetChildren()
.Select(x => x.Value)
.ToList();
string fileType = GetContentTypeFromBase64(base64);
if (allowedFilesType != null && allowedFilesType.Contains(fileType))
{
string fileName = GenerateFileName(fileType, tenantId, tag);
fileBytes = Convert.FromBase64String(base64);
using var fileStream = new MemoryStream(fileBytes);
// Generate a unique object key (you can customize this) // Generate a unique object key (you can customize this)
var objectKey = $"{fileName}"; var objectKey = $"{fileName}";
@ -35,7 +58,7 @@ namespace Marco.Pms.Services.Service
InputStream = fileStream, InputStream = fileStream,
Key = objectKey, Key = objectKey,
BucketName = _bucketName, BucketName = _bucketName,
ContentType = contentType, ContentType = fileType,
AutoCloseStream = true AutoCloseStream = true
}; };
try try
@ -51,9 +74,11 @@ namespace Marco.Pms.Services.Service
return string.Empty; return string.Empty;
} }
} }
public async Task<string> GeneratePreSignedUrlAsync(string objectKey) throw new InvalidOperationException("Unsupported file type.");
}
public string GeneratePreSignedUrlAsync(string objectKey)
{ {
int expiresInMinutes = 1; int expiresInMinutes = 10;
var request = new GetPreSignedUrlRequest var request = new GetPreSignedUrlRequest
{ {
BucketName = _bucketName, BucketName = _bucketName,
@ -95,11 +120,116 @@ namespace Marco.Pms.Services.Service
} }
public string GenerateFileName(string contentType, int tenantId, string? name) public string GenerateFileName(string contentType, int tenantId, string? name)
{ {
string extenstion = contentType.Split("/")[1]; string extenstion = GetExtensionFromMimeType(contentType);
if (string.IsNullOrEmpty(name)) if (string.IsNullOrEmpty(name))
return $"{tenantId}_{DateTime.UtcNow:yyyyMMddHHmmssfff}.{extenstion}"; return $"{tenantId}_{DateTime.UtcNow:yyyyMMddHHmmssfff}{extenstion}";
return $"{name}_{tenantId}_{DateTime.UtcNow:yyyyMMddHHmmssfff}.{extenstion}"; return $"{name}_{tenantId}_{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.LogError("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)
{
// Handle cases where the input string is not valid Base64
Console.WriteLine("Error: Invalid Base64 string.");
return null;
}
catch (Exception ex)
{
// Handle other potential errors during decoding or inspection
Console.WriteLine($"An error occurred: {ex.Message}");
return null;
}
}
} }
} }

View File

@ -86,6 +86,24 @@
"SecretKey": "NTS5XXgZINQbU6ctpNuLXtIY/Qk9GCgD9Rr5yNJP", "SecretKey": "NTS5XXgZINQbU6ctpNuLXtIY/Qk9GCgD9Rr5yNJP",
"Region": "us-east-1", "Region": "us-east-1",
"BucketName": "testenv-marco-pms-documents" "BucketName": "testenv-marco-pms-documents"
},
"WhiteList": {
"ContentType": [
"application/pdf", // pdf
"application/msword", // Doc
"application/vnd.openxmlformats-officedocument.wordprocessingml.document", //docx
"application/vnd.ms-excel", //xls
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", //xlsx
"application/mspowerpoint", //ppt
"application/vnd.openxmlformats-officedocument.presentationml.presentation", //pptx
"text/plain", //txt
"application/rtf", //rtf
"text/csv", //csv
"image/jpg", //jpg
"image/jpeg", //jpeg
"image/png", //png
"image/gif", //gif
"image/bmp" //bmp
]
} }
} }