using Marco.Pms.DataAccess.Data; using Marco.Pms.Model.DocumentManager; using Marco.Pms.Model.Dtos.Forum; using Marco.Pms.Model.Employees; using Marco.Pms.Model.Forum; using Marco.Pms.Model.Mapper; using Marco.Pms.Model.Master; using Marco.Pms.Model.Projects; using Marco.Pms.Model.Utilities; using Marco.Pms.Model.ViewModels.Forum; using Marco.Pms.Services.Service; using MarcoBMS.Services.Helpers; using MarcoBMS.Services.Service; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; namespace Marco.Pms.Services.Controllers { [Authorize] [ApiController] [Route("api/[controller]")] public class ForumController : ControllerBase { private readonly ApplicationDbContext _context; private readonly UserHelper _userHelper; private readonly S3UploadService _s3Service; private readonly ILoggingService _logger; //string tenentId = "1"; public ForumController(ApplicationDbContext context, S3UploadService s3Service, UserHelper userHelper, ILoggingService logger) { _context = context; _userHelper = userHelper; _s3Service = s3Service; _logger = logger; } [HttpPost("ticket")] public async Task CreateNewTicket([FromBody] CreateTicketDto createTicketDto) { if (!ModelState.IsValid) { var errors = ModelState.Values .SelectMany(v => v.Errors) .Select(e => e.ErrorMessage) .ToList(); _logger.LogWarning("{error}", errors); return BadRequest(ApiResponse.ErrorResponse("Invalid data", errors, 400)); } Guid tenantId = _userHelper.GetTenantId(); var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); var batchId = Guid.NewGuid(); TicketForum ticketForum = createTicketDto.ToTicketForumFromCreateTicketDto(tenantId); _context.Tickets.Add(ticketForum); await _context.SaveChangesAsync(); List attachments = new List(); List ticketTags = new List(); List documents = new List(); List tagVMs = new List(); if (createTicketDto.Attachments != null) { foreach (var attachmentDto in createTicketDto.Attachments) { var Image = attachmentDto; if (string.IsNullOrEmpty(Image.Base64Data)) { _logger.LogWarning("Base64 data is missing"); return BadRequest(ApiResponse.ErrorResponse("Base64 data is missing", "Base64 data is missing", 400)); } //If base64 has a data URI prefix, strip it var base64 = Image.Base64Data.Contains(",") ? Image.Base64Data.Substring(Image.Base64Data.IndexOf(",") + 1) : Image.Base64Data; string fileType = _s3Service.GetContentTypeFromBase64(base64); string fileName = _s3Service.GenerateFileName(fileType, tenantId, "forum"); string objectKey = $"tenant-{tenantId}/project-{createTicketDto.LinkedProjectId}/froum/{fileName}"; await _s3Service.UploadFileAsync(base64, fileType, objectKey); Document document = attachmentDto.ToDocumentFromForumAttachmentDto(objectKey, objectKey, createTicketDto.CreatedAt, tenantId, batchId, loggedInEmployee.Id); _context.Documents.Add(document); await _context.SaveChangesAsync(); documents.Add(document); var attachment = attachmentDto.ToTicketAttachmentFromForumAttachmentDto(ticketForum.Id, document.Id); attachments.Add(attachment); } _context.TicketAttachments.AddRange(attachments); } if (createTicketDto.TagIds != null) { List? tagMasters = await _context.TicketTagMasters.Where(t => createTicketDto.TagIds.Contains(t.Id)).ToListAsync(); foreach (var ticketTag in tagMasters) { TicketTagVM tagVM = ticketTag.ToTicketTagVMFromTicketTagMaster(); TicketTag tag = new TicketTag { TicketId = ticketForum.Id, TagId = ticketTag.Id }; tagVMs.Add(tagVM); ticketTags.Add(tag); } _context.TicketTags.AddRange(ticketTags); } await _context.SaveChangesAsync(); var ticket = await _context.Tickets.Include(t => t.TicketTypeMaster).Include(t => t.TicketStatusMaster).Include(t => t.Priority).FirstOrDefaultAsync(t => t.Id == ticketForum.Id); Employee? employee = await _context.Employees.FirstOrDefaultAsync(e => e.Id == ticketForum.CreatedById); ForumTicketVM ticketVM = ticket != null && employee != null ? ticket.ToForumTicketVMFromTicketForum(employee) : new ForumTicketVM(); ticketVM.Tags = tagVMs; List attachmentVMs = new List(); foreach (var attachment in attachments) { var document = documents.Find(d => d.Id == attachment.FileId); string preSignedUrl = string.Empty; if (document != null) { preSignedUrl = _s3Service.GeneratePreSignedUrl(document.S3Key); } attachmentVMs.Add(attachment.ToTicketAttachmentVMFromTicketAttachment(preSignedUrl, preSignedUrl)); } ticketVM.Attachments = attachmentVMs; Project project = await _context.Projects.FirstOrDefaultAsync(p => p.Id == ticketForum.LinkedProjectId) ?? new Project(); ticketVM.ProjectName = project.Name; if (createTicketDto.LinkedActivityId != null && createTicketDto.LinkedActivityId != null) { WorkItem workItem = await _context.WorkItems.Include(w => w.ActivityMaster).FirstOrDefaultAsync(w => w.Id == ticketForum.LinkedActivityId) ?? new WorkItem(); if (workItem.ActivityMaster != null) { ticketVM.ActivityName = workItem.ActivityMaster.ActivityName; } else { ticketVM.ActivityName = ""; } } _logger.LogInfo("Ticket created by Employee {EmployeeId} for project {ProjectId}", ticketForum.CreatedById, project.Id); return Ok(ApiResponse.SuccessResponse(ticketVM, "Ticket Created Successfully", 200)); } [HttpPost("ticket/edit")] public async Task UpdateNewTicket([FromBody] UpdateTicketDto updateTicketDto) { if (!ModelState.IsValid) { var errors = ModelState.Values .SelectMany(v => v.Errors) .Select(e => e.ErrorMessage) .ToList(); _logger.LogWarning("{error}", errors); return BadRequest(ApiResponse.ErrorResponse("Invalid data", errors, 400)); } Guid tenantId = _userHelper.GetTenantId(); var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); var batchId = Guid.NewGuid(); var existingTicket = await _context.Tickets .Include(t => t.TicketTypeMaster) .Include(t => t.TicketStatusMaster) .Include(t => t.Priority) .AsNoTracking() .FirstOrDefaultAsync(t => t.Id == updateTicketDto.Id); if (existingTicket != null) { TicketForum ticketForum = updateTicketDto.ToTicketForumFromUpdateTicketDto(existingTicket); _context.Tickets.Update(ticketForum); await _context.SaveChangesAsync(); List attachments = new List(); List ticketTags = new List(); List tagVMs = new List(); List existingAttachments = await _context.TicketAttachments.Where(a => a.TicketId == updateTicketDto.Id).ToListAsync(); var existingattachmentids = existingAttachments.Select(a => a.Id).ToList(); List attachmentDtoids = new List(); if (updateTicketDto.Attachments != null) { attachmentDtoids = updateTicketDto.Attachments.Select(a => a.Id).ToList(); foreach (var attachmentDto in updateTicketDto.Attachments) { if (!existingattachmentids.Contains(attachmentDto.Id) && attachmentDto.TicketId != updateTicketDto.Id) { var Image = attachmentDto; if (string.IsNullOrEmpty(Image.Base64Data)) { _logger.LogWarning("Base64 data is missing"); return BadRequest(ApiResponse.ErrorResponse("Base64 data is missing", "Base64 data is missing", 400)); } //If base64 has a data URI prefix, strip it var base64 = Image.Base64Data.Contains(",") ? Image.Base64Data.Substring(Image.Base64Data.IndexOf(",") + 1) : Image.Base64Data; string fileType = _s3Service.GetContentTypeFromBase64(base64); string fileName = _s3Service.GenerateFileName(fileType, tenantId, "forum"); string objectKey = $"tenant-{tenantId}/project-{updateTicketDto.LinkedProjectId}/froum/{fileName}"; await _s3Service.UploadFileAsync(base64, fileType, objectKey); Document document = attachmentDto.ToDocumentFromUpdateAttachmentDto(objectKey, objectKey, updateTicketDto.CreatedAt, tenantId, batchId, loggedInEmployee.Id); _context.Documents.Add(document); await _context.SaveChangesAsync(); var attachment = attachmentDto.ToTicketAttachmentFromUpdateAttachmentDto(ticketForum.Id, document.Id); attachments.Add(attachment); } } } if (attachments.Count != 0) { _context.TicketAttachments.AddRange(attachments); } var deletedAttachments = existingAttachments.Where(a => !attachmentDtoids.Contains(a.Id) && a.CommentId == null).ToList(); var deletedFileIds = deletedAttachments.Select(a => a.FileId).ToList(); if (deletedFileIds.Count != 0) { List existingDocuments = await _context.Documents.Where(d => deletedFileIds.Contains(d.Id)).ToListAsync(); foreach (var existingDocument in existingDocuments) { await _s3Service.DeleteFileAsync(existingDocument.S3Key); //await _s3Service.DeleteFileAsync(existingDocument.ThumbS3Key); } _context.TicketAttachments.RemoveRange(deletedAttachments); } List existingTicketTags = await _context.TicketTags.Where(t => t.TicketId == updateTicketDto.Id).ToListAsync(); List? tagMasters = await _context.TicketTagMasters.Where(t => updateTicketDto.TagIds != null ? updateTicketDto.TagIds.Contains(t.Id) : false).ToListAsync(); var existingTicketTagIds = existingTicketTags.Select(t => t.TagId).ToList(); foreach (var ticketTag in tagMasters) { TicketTagVM tagVM = ticketTag.ToTicketTagVMFromTicketTagMaster(); TicketTag tag = new TicketTag { TicketId = ticketForum.Id, TagId = ticketTag.Id }; tagVMs.Add(tagVM); var demo = !existingTicketTagIds.Contains(tag.TagId); if (!existingTicketTagIds.Contains(tag.TagId)) { ticketTags.Add(tag); } } if (ticketTags != null) { _context.TicketTags.AddRange(ticketTags); } var deletedTicketTags = existingTicketTags.Where(t => updateTicketDto.TagIds != null ? !updateTicketDto.TagIds.Contains(t.TagId) : true).ToList(); if (deletedTicketTags.Count != 0) { _context.TicketTags.RemoveRange(deletedTicketTags); } await _context.SaveChangesAsync(); var ticket = await _context.Tickets.Include(t => t.TicketTypeMaster).Include(t => t.TicketStatusMaster).Include(t => t.Priority).FirstOrDefaultAsync(t => t.Id == ticketForum.Id); ForumTicketVM ticketVM = new ForumTicketVM(); if (ticket != null) { List comments = await _context.TicketComments.Where(c => c.TicketId == ticket.Id).ToListAsync(); var authorIds = comments.Select(c => c.AuthorId.ToString()).ToList(); List? employees = await _context.Employees.Where(e => e.Id == ticketForum.CreatedById || authorIds.Contains(e.ApplicationUserId ?? "")).ToListAsync(); Employee employee = employees.Find(e => e.Id == ticketForum.CreatedById) ?? new Employee(); ticketVM = ticket.ToForumTicketVMFromTicketForum(employee); ticketVM.Tags = tagVMs; List commentVMs = new List(); foreach (var comment in comments) { employee = employees.Find(e => e.ApplicationUserId == comment.AuthorId.ToString()) ?? new Employee(); commentVMs.Add(comment.ToTicketCommentVMFromTicketComment(employee)); } attachments = await _context.TicketAttachments.Where(a => a.TicketId == updateTicketDto.Id).ToListAsync(); var fileIds = attachments.Select(a => a.FileId).ToList(); List documents = await _context.Documents.Where(d => fileIds.Contains(d.Id)).ToListAsync(); List ticketAttachmentVMs = new List(); foreach (var attachment in attachments) { var document = documents.Find(d => d.Id == attachment.FileId); string preSignedUrl = string.Empty; if (document != null) { preSignedUrl = _s3Service.GeneratePreSignedUrl(document.S3Key); } if (attachment.CommentId == null) { ticketAttachmentVMs.Add(attachment.ToTicketAttachmentVMFromTicketAttachment(preSignedUrl, preSignedUrl)); } else { var commentVM = commentVMs.Find(c => c.Id == attachment.CommentId); if (commentVM != null && commentVM.Attachments != null) { commentVM.Attachments.Add(attachment.ToTicketAttachmentVMFromTicketAttachment(preSignedUrl, preSignedUrl)); } } } ticketVM.Comments = commentVMs; ticketVM.Attachments = ticketAttachmentVMs; Project project = await _context.Projects.FirstOrDefaultAsync(p => p.Id == ticketForum.LinkedProjectId) ?? new Project(); ticketVM.ProjectName = project.Name; if (updateTicketDto.LinkedActivityId != null && updateTicketDto.LinkedActivityId != null) { WorkItem workItem = await _context.WorkItems.Include(w => w.ActivityMaster).FirstOrDefaultAsync(w => w.Id == ticketForum.LinkedActivityId) ?? new WorkItem(); if (workItem.ActivityMaster != null) { ticketVM.ActivityName = workItem.ActivityMaster.ActivityName; } else { ticketVM.ActivityName = ""; } } } _logger.LogInfo("Ticket {TicketId} updated", updateTicketDto.Id); return Ok(ApiResponse.SuccessResponse(ticketVM, "Ticket Updated Successfully", 200)); } _logger.LogWarning("Ticket {TicketId} not Found in database", updateTicketDto.Id); return NotFound(ApiResponse.ErrorResponse("Ticket not Found", "Ticket not Found", 404)); } [HttpPost("ticket/comment")] public async Task AddComment([FromBody] AddCommentDto addCommentDto) { if (!ModelState.IsValid) { var errors = ModelState.Values .SelectMany(v => v.Errors) .Select(e => e.ErrorMessage) .ToList(); _logger.LogWarning("{error}", errors); return BadRequest(ApiResponse.ErrorResponse("Invalid data", errors, 400)); } Guid tenantId = _userHelper.GetTenantId(); var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); var batchId = Guid.NewGuid(); List attachments = new List(); List documents = new List(); TicketForum? ticket = await _context.Tickets.FirstOrDefaultAsync(t => t.Id == addCommentDto.TicketId); if (ticket == null) { _logger.LogWarning("Ticket {TicketId} not Found in database", addCommentDto.TicketId); return NotFound(ApiResponse.ErrorResponse("Ticket not Found", "Ticket not Found", 404)); } TicketComment comment = addCommentDto.ToTicketCommentFromAddCommentDto(tenantId); _context.TicketComments.Add(comment); await _context.SaveChangesAsync(); if (addCommentDto.Attachments != null) { foreach (var attachmentDto in addCommentDto.Attachments) { var Image = attachmentDto; if (string.IsNullOrEmpty(Image.Base64Data)) { _logger.LogWarning("Base64 data is missing"); return BadRequest(ApiResponse.ErrorResponse("Base64 data is missing", "Base64 data is missing", 400)); } //If base64 has a data URI prefix, strip it var base64 = Image.Base64Data.Contains(",") ? Image.Base64Data.Substring(Image.Base64Data.IndexOf(",") + 1) : Image.Base64Data; string fileType = _s3Service.GetContentTypeFromBase64(base64); string fileName = _s3Service.GenerateFileName(fileType, tenantId, "forum"); string objectKey = $"tenant-{tenantId}/project-{ticket.LinkedProjectId}/froum/{fileName}"; await _s3Service.UploadFileAsync(base64, fileType, objectKey); Document document = attachmentDto.ToDocumentFromForumAttachmentDto(objectKey, objectKey, addCommentDto.SentAt, tenantId, batchId, loggedInEmployee.Id); _context.Documents.Add(document); await _context.SaveChangesAsync(); documents.Add(document); var attachment = attachmentDto.ToTicketAttachmentFromForumAttachmentDto(addCommentDto.TicketId, document.Id, comment.Id); attachments.Add(attachment); } _context.TicketAttachments.AddRange(attachments); await _context.SaveChangesAsync(); } Employee employee = await _context.Employees.FirstOrDefaultAsync(e => e.Id == addCommentDto.AuthorId) ?? new Employee(); TicketCommentVM commentVM = comment.ToTicketCommentVMFromTicketComment(employee); List attachmentVMs = new List(); if (attachments != null) { foreach (var attachment in attachments) { var document = documents.Find(d => d.Id == attachment.FileId); string preSignedUrl = string.Empty; if (document != null) { preSignedUrl = _s3Service.GeneratePreSignedUrl(document.S3Key); } attachmentVMs.Add(attachment.ToTicketAttachmentVMFromTicketAttachment(preSignedUrl, preSignedUrl)); } } commentVM.Attachments = attachmentVMs; _logger.LogInfo("User {ApplicationUserId} commented on a ticket{TicketId}", comment.AuthorId, comment.TicketId); return Ok(ApiResponse.SuccessResponse(commentVM, "Comment Created Successfully", 200)); } [HttpPost("ticket/comment/edit")] public async Task EditComment([FromBody] UpdateCommentDto updateCommentDto) { if (!ModelState.IsValid) { var errors = ModelState.Values .SelectMany(v => v.Errors) .Select(e => e.ErrorMessage) .ToList(); _logger.LogWarning("{error}", errors); return BadRequest(ApiResponse.ErrorResponse("Invalid data", errors, 400)); } Guid tenantId = _userHelper.GetTenantId(); var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); var batchId = Guid.NewGuid(); List attachments = new List(); TicketForum? ticket = await _context.Tickets.FirstOrDefaultAsync(t => t.Id == updateCommentDto.TicketId); if (ticket == null) { _logger.LogWarning("Ticket {TicketId} not Found in database", updateCommentDto.TicketId); return NotFound(ApiResponse.ErrorResponse("Ticket not Found", "Ticket not Found", 404)); } TicketComment existingComment = await _context.TicketComments.AsNoTracking().FirstOrDefaultAsync(c => c.Id == updateCommentDto.Id) ?? new TicketComment(); TicketComment updateComment = updateCommentDto.ToTicketCommentFromUpdateCommentDto(tenantId, existingComment); _context.TicketComments.Update(updateComment); await _context.SaveChangesAsync(); List existingAttachments = await _context.TicketAttachments.Where(a => a.CommentId == updateComment.Id).ToListAsync(); var existingattachmentids = existingAttachments.Select(a => a.Id).ToList(); List attachmentDtoids = new List(); if (updateCommentDto.Attachments != null) { attachmentDtoids = updateCommentDto.Attachments.Select(a => a.Id).ToList(); foreach (var attachmentDto in updateCommentDto.Attachments) { if (!existingattachmentids.Contains(attachmentDto.Id) && attachmentDto.CommentId != updateComment.Id) { var Image = attachmentDto; if (string.IsNullOrEmpty(Image.Base64Data)) { _logger.LogWarning("Base64 data is missing"); return BadRequest(ApiResponse.ErrorResponse("Base64 data is missing", "Base64 data is missing", 400)); } //If base64 has a data URI prefix, strip it var base64 = Image.Base64Data.Contains(",") ? Image.Base64Data.Substring(Image.Base64Data.IndexOf(",") + 1) : Image.Base64Data; string fileType = _s3Service.GetContentTypeFromBase64(base64); string fileName = _s3Service.GenerateFileName(fileType, tenantId, "forum"); string objectKey = $"tenant-{tenantId}/project-{ticket.LinkedProjectId}/froum/{fileName}"; await _s3Service.UploadFileAsync(base64, fileType, objectKey); Document document = attachmentDto.ToDocumentFromUpdateAttachmentDto(objectKey, objectKey, existingComment.SentAt, tenantId, batchId, loggedInEmployee.Id); _context.Documents.Add(document); await _context.SaveChangesAsync(); var attachment = attachmentDto.ToTicketAttachmentFromUpdateAttachmentDto(existingComment.TicketId, document.Id, updateComment.Id); attachments.Add(attachment); } } } if (attachments.Count != 0) { _context.TicketAttachments.AddRange(attachments); } var deletedAttachments = existingAttachments.Where(a => !attachmentDtoids.Contains(a.Id) && a.CommentId != null).ToList(); var deletedFileIds = deletedAttachments.Select(a => a.FileId).ToList(); if (deletedFileIds.Count != 0) { List existingDocuments = await _context.Documents.Where(d => deletedFileIds.Contains(d.Id)).ToListAsync(); foreach (var existingDocument in existingDocuments) { await _s3Service.DeleteFileAsync(existingDocument.S3Key); //await _s3Service.DeleteFileAsync(existingDocument.ThumbS3Key); } _context.TicketAttachments.RemoveRange(deletedAttachments); } await _context.SaveChangesAsync(); Employee employee = await _context.Employees.FirstOrDefaultAsync(e => e.Id == existingComment.AuthorId) ?? new Employee(); TicketCommentVM commentVM = updateComment.ToTicketCommentVMFromTicketComment(employee); List attachmentVMs = new List(); attachments = await _context.TicketAttachments.Where(a => a.CommentId == updateCommentDto.Id).ToListAsync(); var fileIds = attachments.Select(a => a.FileId).ToList(); List documents = await _context.Documents.Where(d => fileIds.Contains(d.Id)).ToListAsync(); if (attachments != null) { foreach (var attachment in attachments) { var document = documents.Find(d => d.Id == attachment.FileId); string preSignedUrl = string.Empty; if (document != null) { preSignedUrl = _s3Service.GeneratePreSignedUrl(document.S3Key); } attachmentVMs.Add(attachment.ToTicketAttachmentVMFromTicketAttachment(preSignedUrl, preSignedUrl)); } } commentVM.Attachments = attachmentVMs; _logger.LogInfo("comment {CommentId} was Updated", updateComment.Id); return Ok(ApiResponse.SuccessResponse(commentVM, "Comment Updated Successfully", 200)); } [HttpPost("ticket/attachment")] public async Task UploadAttachments([FromBody] List forumAttachmentDtos) { if (!ModelState.IsValid) { var errors = ModelState.Values .SelectMany(v => v.Errors) .Select(e => e.ErrorMessage) .ToList(); _logger.LogWarning("{error}", errors); return BadRequest(ApiResponse.ErrorResponse("Invalid data", errors, 400)); } Guid tenantId = _userHelper.GetTenantId(); var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); var batchId = Guid.NewGuid(); List ticketAttachmentVMs = new List(); List ticketIds = forumAttachmentDtos.Select(f => f.TicketId.HasValue ? f.TicketId.Value : Guid.Empty).ToList(); List tickets = await _context.Tickets.Where(t => ticketIds.Contains(t.Id)).ToListAsync(); if (tickets == null || tickets.Count > 0) { _logger.LogWarning("Tickets not Found in database"); return NotFound(ApiResponse.ErrorResponse("Ticket not Found", "Ticket not Found", 404)); } TicketAttachment attachment = new TicketAttachment(); foreach (var forumAttachmentDto in forumAttachmentDtos) { if (string.IsNullOrEmpty(forumAttachmentDto.Base64Data)) { _logger.LogWarning("Base64 data is missing"); return BadRequest(ApiResponse.ErrorResponse("Base64 data is missing", "Base64 data is missing", 400)); } if (forumAttachmentDto.TicketId == null) { _logger.LogWarning("ticket ID is missing"); return BadRequest(ApiResponse.ErrorResponse("ticket ID is missing", "ticket ID is missing", 400)); } var ticket = tickets.FirstOrDefault(t => t.Id == forumAttachmentDto.TicketId); //If base64 has a data URI prefix, strip it var base64 = forumAttachmentDto.Base64Data.Contains(",") ? forumAttachmentDto.Base64Data.Substring(forumAttachmentDto.Base64Data.IndexOf(",") + 1) : forumAttachmentDto.Base64Data; string fileType = _s3Service.GetContentTypeFromBase64(base64); string fileName = _s3Service.GenerateFileName(fileType, tenantId, "forum"); string objectKey = $"tenant-{tenantId}/project-{ticket?.LinkedProjectId}/froum/{fileName}"; await _s3Service.UploadFileAsync(base64, fileType, objectKey); Document document = forumAttachmentDto.ToDocumentFromForumAttachmentDto(objectKey, objectKey, forumAttachmentDto.SentAt, tenantId, batchId, loggedInEmployee.Id); _context.Documents.Add(document); await _context.SaveChangesAsync(); attachment = forumAttachmentDto.ToTicketAttachmentFromForumAttachmentDto(forumAttachmentDto.TicketId != null ? forumAttachmentDto.TicketId.Value : Guid.Empty, document.Id, forumAttachmentDto.CommentId); _context.TicketAttachments.Add(attachment); await _context.SaveChangesAsync(); string preSignedUrl = _s3Service.GeneratePreSignedUrl(document.S3Key); TicketAttachmentVM attachmentVM = attachment.ToTicketAttachmentVMFromTicketAttachment(preSignedUrl, preSignedUrl); ticketAttachmentVMs.Add(attachmentVM); } if (attachment.CommentId == null) { _logger.LogInfo("Attachments were added to ticket {TicketId}", attachment.TicketId); } else { _logger.LogInfo("Attachments were added to comment {CommentId}", attachment.CommentId); } return Ok(ApiResponse.SuccessResponse(ticketAttachmentVMs, "attechments added Successfully", 200)); } [HttpPatch("ticket/status/{id}")] public async Task UpdateTicketStatus(Guid id, [FromQuery] Guid statusId) { Guid tenantId = _userHelper.GetTenantId(); TicketForum ticket = await _context.Tickets.FirstOrDefaultAsync(t => t.Id == id && t.TenantId == tenantId) ?? new TicketForum(); ticket.StatusId = statusId; await _context.SaveChangesAsync(); ticket = await _context.Tickets.Include(t => t.TicketTypeMaster).Include(t => t.TicketStatusMaster).Include(t => t.Priority).FirstOrDefaultAsync(t => t.Id == id && t.TenantId == tenantId) ?? new TicketForum(); List comments = await _context.TicketComments.Where(c => c.TicketId == ticket.Id).ToListAsync(); var authorIds = comments.Select(c => c.AuthorId.ToString()).ToList(); List employees = await _context.Employees.Where(e => e.Id == ticket.CreatedById || authorIds.Contains(e.ApplicationUserId ?? "")).ToListAsync(); Employee employee = employees.Find(e => e.Id == ticket.CreatedById) ?? new Employee(); ForumTicketVM ticketVM = ticket.ToForumTicketVMFromTicketForum(employee); var ticketTags = await _context.TicketTags.Where(t => t.TicketId == ticket.Id).ToListAsync(); List tagIds = ticketTags.Select(t => t.TagId).ToList(); List? tagMasters = await _context.TicketTagMasters.Where(t => tagIds.Contains(t.Id)).ToListAsync(); List tagVMs = new List(); foreach (var ticketTag in tagMasters) { TicketTagVM tagVM = ticketTag.ToTicketTagVMFromTicketTagMaster(); tagVMs.Add(tagVM); } ticketVM.Tags = tagVMs; List commentVMs = new List(); foreach (var comment in comments) { employee = employees.Find(e => e.ApplicationUserId == comment.AuthorId.ToString()) ?? new Employee(); commentVMs.Add(comment.ToTicketCommentVMFromTicketComment(employee)); } List attachments = await _context.TicketAttachments.Where(a => a.TicketId == id).ToListAsync(); List fileIds = attachments.Select(a => a.FileId).ToList(); List documents = await _context.Documents.Where(d => fileIds.Contains(d.Id)).ToListAsync(); List ticketAttachmentVMs = new List(); foreach (var attachment in attachments) { var document = documents.Find(d => d.Id == attachment.FileId); string preSignedUrl = string.Empty; if (document != null) { preSignedUrl = _s3Service.GeneratePreSignedUrl(document.S3Key); } if (attachment.CommentId == null) { ticketAttachmentVMs.Add(attachment.ToTicketAttachmentVMFromTicketAttachment(preSignedUrl, preSignedUrl)); } else { var commentVM = commentVMs.Find(c => c.Id == attachment.CommentId); if (commentVM != null && commentVM.Attachments != null) { commentVM.Attachments.Add(attachment.ToTicketAttachmentVMFromTicketAttachment(preSignedUrl, preSignedUrl)); } } } ticketVM.Comments = commentVMs; ticketVM.Attachments = ticketAttachmentVMs; Project project = await _context.Projects.FirstOrDefaultAsync(p => p.Id == ticket.LinkedProjectId) ?? new Project(); ticketVM.ProjectName = project.Name; if (ticket.LinkedActivityId != null && ticket.LinkedActivityId != null) { WorkItem workItem = await _context.WorkItems.Include(w => w.ActivityMaster).FirstOrDefaultAsync(w => w.Id == ticket.LinkedActivityId) ?? new WorkItem(); if (workItem.ActivityMaster != null) { ticketVM.ActivityName = workItem.ActivityMaster.ActivityName; } else { ticketVM.ActivityName = ""; } } _logger.LogInfo("Status of Ticket {TicketId} in project {ProjectId} is changes to {status}", id, ticket.LinkedProjectId ?? Guid.Empty, ticket.TicketStatusMaster != null ? ticket.TicketStatusMaster.Name : string.Empty); return Ok(ApiResponse.SuccessResponse(ticketVM, "Ticket Fetched Successfully", 200)); } [HttpGet("ticket/{id}")] public async Task GetTicketDetail(Guid id) { Guid tenantId = _userHelper.GetTenantId(); TicketForum ticket = await _context.Tickets.Include(t => t.TicketTypeMaster).Include(t => t.TicketStatusMaster).Include(t => t.Priority).FirstOrDefaultAsync(t => t.Id == id && t.TenantId == tenantId) ?? new TicketForum(); List comments = await _context.TicketComments.Where(c => c.TicketId == ticket.Id).ToListAsync(); var authorIds = comments.Select(c => c.AuthorId.ToString()).ToList(); List employees = await _context.Employees.Where(e => e.Id == ticket.CreatedById || authorIds.Contains(e.ApplicationUserId ?? "")).ToListAsync(); Employee employee = employees.Find(e => e.Id == ticket.CreatedById) ?? new Employee(); ForumTicketVM ticketVM = ticket.ToForumTicketVMFromTicketForum(employee); var ticketTags = await _context.TicketTags.Where(t => t.TicketId == ticket.Id).ToListAsync(); List tagIds = ticketTags.Select(t => t.TagId).ToList(); List? tagMasters = await _context.TicketTagMasters.Where(t => tagIds.Contains(t.Id)).ToListAsync(); List tagVMs = new List(); foreach (var ticketTag in tagMasters) { TicketTagVM tagVM = ticketTag.ToTicketTagVMFromTicketTagMaster(); tagVMs.Add(tagVM); } ticketVM.Tags = tagVMs; List commentVMs = new List(); foreach (var comment in comments) { employee = employees.Find(e => e.ApplicationUserId == comment.AuthorId.ToString()) ?? new Employee(); commentVMs.Add(comment.ToTicketCommentVMFromTicketComment(employee)); } List attachments = await _context.TicketAttachments.Where(a => a.TicketId == id).ToListAsync(); List fileIds = attachments.Select(a => a.FileId).ToList(); List documents = await _context.Documents.Where(d => fileIds.Contains(d.Id)).ToListAsync(); List ticketAttachmentVMs = new List(); foreach (var attachment in attachments) { var document = documents.Find(d => d.Id == attachment.FileId); string preSignedUrl = string.Empty; if (document != null) { preSignedUrl = _s3Service.GeneratePreSignedUrl(document.S3Key); } if (attachment.CommentId == null) { ticketAttachmentVMs.Add(attachment.ToTicketAttachmentVMFromTicketAttachment(preSignedUrl, preSignedUrl)); } else { var commentVM = commentVMs.Find(c => c.Id == attachment.CommentId); if (commentVM != null && commentVM.Attachments != null) { commentVM.Attachments.Add(attachment.ToTicketAttachmentVMFromTicketAttachment(preSignedUrl, preSignedUrl)); } } } ticketVM.Comments = commentVMs; ticketVM.Attachments = ticketAttachmentVMs; Project project = await _context.Projects.FirstOrDefaultAsync(p => p.Id == ticket.LinkedProjectId) ?? new Project(); ticketVM.ProjectName = project.Name; if (ticket.LinkedActivityId != null && ticket.LinkedActivityId != null) { WorkItem workItem = await _context.WorkItems.Include(w => w.ActivityMaster).FirstOrDefaultAsync(w => w.Id == ticket.LinkedActivityId) ?? new WorkItem(); if (workItem.ActivityMaster != null) { ticketVM.ActivityName = workItem.ActivityMaster.ActivityName; } else { ticketVM.ActivityName = ""; } } _logger.LogInfo("Fetched Ticket {TicketId} form project {ProjectId}", id, ticket.LinkedProjectId ?? Guid.Empty); return Ok(ApiResponse.SuccessResponse(ticketVM, "Ticket Fetched Successfully", 200)); } [HttpGet("tickets")] public async Task ListTickets() { Guid tenantId = _userHelper.GetTenantId(); List tickets = await _context.Tickets.Include(t => t.TicketTypeMaster).Include(t => t.TicketStatusMaster).Include(t => t.Priority).Where(t => t.TenantId == tenantId).ToListAsync(); var createdByIds = tickets.Select(t => t.CreatedById).ToList(); var ticketIds = tickets.Select(t => t.Id).ToList(); var linkedActivityIds = tickets.Select(t => t.LinkedActivityId).ToList(); var linkedProjectIds = tickets.Select(t => t.LinkedProjectId).ToList(); List ticketComments = await _context.TicketComments.Where(c => ticketIds.Contains(c.TicketId)).ToListAsync(); var authorIds = ticketComments.Select(c => c.AuthorId.ToString()).ToList(); List employees = await _context.Employees.Where(e => createdByIds.Contains(e.Id) || (e.ApplicationUserId != null && authorIds.Contains(e.ApplicationUserId))).ToListAsync(); var ticketTags = await _context.TicketTags.Where(t => ticketIds.Contains(t.TicketId)).ToListAsync(); List tagIds = ticketTags.Select(t => t.TagId).ToList(); List? tagMasters = await _context.TicketTagMasters.Where(t => tagIds.Contains(t.Id)).ToListAsync(); List ticketAttachments = await _context.TicketAttachments.Where(a => ticketIds.Contains(a.TicketId)).ToListAsync(); List documentIds = ticketAttachments.Select(a => a.FileId).ToList(); List documents = await _context.Documents.Where(d => documentIds.Contains(d.Id)).ToListAsync(); List projects = await _context.Projects.Where(p => linkedProjectIds.Contains(p.Id)).ToListAsync() ?? new List(); List workItems = await _context.WorkItems.Include(w => w.ActivityMaster).Where(w => linkedActivityIds.Contains(w.Id)).ToListAsync() ?? new List(); List ticketVMs = new List(); foreach (var ticket in tickets) { Employee employee = employees.Find(e => e.Id == ticket.CreatedById) ?? new Employee(); ForumTicketVM ticketVM = ticket.ToForumTicketVMFromTicketForum(employee); var ticketTagMappers = ticketTags.Where(t => t.TicketId == ticket.Id).ToList(); List ticketTagIds = ticketTagMappers.Select(t => t.TagId).ToList(); List ticketTagMasters = tagMasters.Where(t => ticketTagIds.Contains(t.Id)).ToList(); List tagVMs = new List(); foreach (var ticketTag in ticketTagMasters) { TicketTagVM tagVM = ticketTag.ToTicketTagVMFromTicketTagMaster(); tagVMs.Add(tagVM); } List comments = ticketComments.Where(c => c.TicketId == ticket.Id).ToList(); List commentVMs = new List(); foreach (var comment in comments) { employee = employees.Find(e => e.ApplicationUserId == comment.AuthorId.ToString()) ?? new Employee(); commentVMs.Add(comment.ToTicketCommentVMFromTicketComment(employee)); } List attachments = ticketAttachments.Where(a => a.TicketId == ticket.Id).ToList(); List fileIds = attachments.Select(a => a.FileId).ToList(); List files = documents.Where(d => fileIds.Contains(d.Id)).ToList(); List ticketAttachmentVMs = new List(); foreach (var attachment in attachments) { var document = documents.Find(d => d.Id == attachment.FileId); string preSignedUrl = string.Empty; if (document != null) { preSignedUrl = _s3Service.GeneratePreSignedUrl(document.S3Key); } if (attachment.CommentId == null) { ticketAttachmentVMs.Add(attachment.ToTicketAttachmentVMFromTicketAttachment(preSignedUrl, preSignedUrl)); } else { var commentVM = commentVMs.Find(c => c.Id == attachment.CommentId); if (commentVM != null && commentVM.Attachments != null) { commentVM.Attachments.Add(attachment.ToTicketAttachmentVMFromTicketAttachment(preSignedUrl, preSignedUrl)); } } } ticketVM.Comments = commentVMs; ticketVM.Attachments = ticketAttachmentVMs; ticketVM.Tags = tagVMs; Project project = projects.Find(p => p.Id == ticket.LinkedProjectId) ?? new Project(); ticketVM.ProjectName = project.Name; if (ticket.LinkedActivityId != null && ticket.LinkedActivityId != null) { WorkItem workItem = workItems.Find(w => w.Id == ticket.LinkedActivityId) ?? new WorkItem(); if (workItem.ActivityMaster != null) { ticketVM.ActivityName = workItem.ActivityMaster.ActivityName; } else { ticketVM.ActivityName = ""; } } ticketVMs.Add(ticketVM); } _logger.LogInfo("{count} tickets records fetched successfully from tenant {tenantId}", ticketVMs.Count, tenantId); return Ok(ApiResponse.SuccessResponse(ticketVMs, System.String.Format("{0} tickets records fetched successfully", ticketVMs.Count), 200)); } } }