316 lines
14 KiB
C#

using Marco.Pms.DataAccess.Data;
using Marco.Pms.Model.Activities;
using Marco.Pms.Model.Employees;
using Marco.Pms.Model.Mapper;
using Marco.Pms.Model.Projects;
using Marco.Pms.Model.Utilities;
using Marco.Pms.Services.Service;
using MarcoBMS.Services.Helpers;
using MarcoBMS.Services.Service;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace Marco.Pms.Services.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class ImageController : ControllerBase
{
private readonly ApplicationDbContext _context;
private readonly S3UploadService _s3Service;
private readonly UserHelper _userHelper;
private readonly ILoggingService _logger;
private readonly PermissionServices _permission;
private readonly Guid tenantId;
public ImageController(ApplicationDbContext context, S3UploadService s3Service, UserHelper userHelper, ILoggingService logger, PermissionServices permission)
{
_context = context;
_s3Service = s3Service;
_userHelper = userHelper;
_logger = logger;
tenantId = userHelper.GetTenantId();
_permission = permission;
}
[HttpGet("images/{projectId}")]
public async Task<IActionResult> GetImageList(Guid projectId)
{
_logger.LogInfo("GetImageList called for ProjectId: {ProjectId}", projectId);
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
// Step 1: Validate project existence
var isProjectExist = await _context.Projects.AnyAsync(p => p.Id == projectId && p.TenantId == tenantId);
if (!isProjectExist)
{
_logger.LogWarning("Project not found for ProjectId: {ProjectId}", projectId);
return BadRequest(ApiResponse<object>.ErrorResponse("Project not found", "Project not found in database", 400));
}
// Step 2: Check permission
var hasPermission = await _permission.HasProjectPermission(loggedInEmployee, projectId.ToString());
if (!hasPermission)
{
_logger.LogWarning("No access to ProjectId: {ProjectId} for EmployeeId: {EmployeeId}", projectId, loggedInEmployee.Id);
return StatusCode(403, ApiResponse<object>.ErrorResponse("You don't have access", "You don't have access", 403));
}
// Step 3: Fetch building > floor > work area > work item hierarchy
var buildings = await _context.Buildings
.Where(b => b.ProjectId == projectId)
.Select(b => new { b.Id, b.Name })
.ToListAsync();
var buildingIds = buildings.Select(b => b.Id).ToList();
var floors = await _context.Floor
.Where(f => buildingIds.Contains(f.BuildingId))
.Select(f => new { f.Id, f.BuildingId, f.FloorName })
.ToListAsync();
var floorIds = floors.Select(f => f.Id).ToList();
var workAreas = await _context.WorkAreas
.Where(wa => floorIds.Contains(wa.FloorId))
.Select(wa => new { wa.Id, wa.FloorId, wa.AreaName })
.ToListAsync();
var workAreaIds = workAreas.Select(wa => wa.Id).ToList();
var workItems = await _context.WorkItems
.Include(wi => wi.ActivityMaster)
.Where(wi => workAreaIds.Contains(wi.WorkAreaId))
.Select(wi => new { wi.Id, wi.WorkAreaId, wi.ActivityMaster })
.ToListAsync();
var workItemIds = workItems.Select(wi => wi.Id).ToList();
// Step 4: Fetch task and comment data
var tasks = await _context.TaskAllocations
.Include(t => t.ReportedBy)
.Where(t => workItemIds.Contains(t.WorkItemId))
.ToListAsync();
var taskIds = tasks.Select(t => t.Id).ToList();
var comments = await _context.TaskComments
.Include(c => c.Employee)
.Where(c => taskIds.Contains(c.TaskAllocationId))
.ToListAsync();
var commentIds = comments.Select(c => c.Id).ToList();
// Step 5: Fetch attachments and related documents
var attachments = await _context.TaskAttachments
.Where(ta => taskIds.Contains(ta.ReferenceId) || commentIds.Contains(ta.ReferenceId))
.ToListAsync();
var documentIds = attachments.Select(ta => ta.DocumentId).ToList();
var documents = await _context.Documents
.Include(d => d.UploadedBy)
.Where(d => documentIds.Contains(d.Id))
.ToListAsync();
// Step 6: Prepare view models
var documentVM = documents
.Select(d =>
{
var referenceId = attachments
.Where(ta => ta.DocumentId == d.Id)
.Select(ta => ta.ReferenceId)
.FirstOrDefault();
var task = tasks.FirstOrDefault(t => t.Id == referenceId);
var comment = comments.FirstOrDefault(c => c.Id == referenceId);
string source = "";
Employee? uploadedBy = null;
if (task != null)
{
uploadedBy = task.ReportedBy;
source = "Report";
}
else if (comment != null)
{
task = tasks.FirstOrDefault(t => t.Id == comment.TaskAllocationId);
uploadedBy = comment.Employee;
source = "Comment";
}
var workItem = workItems.FirstOrDefault(wi => wi.Id == task?.WorkItemId);
var workArea = workAreas.FirstOrDefault(wa => wa.Id == workItem?.WorkAreaId);
var floor = floors.FirstOrDefault(f => f.Id == workArea?.FloorId);
var building = buildings.FirstOrDefault(b => b.Id == floor?.BuildingId);
return new
{
Id = d.Id,
BatchId = d.BatchId,
thumbnailUrl = d.ThumbS3Key != null ? _s3Service.GeneratePreSignedUrlAsync(d.ThumbS3Key) : (d.S3Key != null ? _s3Service.GeneratePreSignedUrlAsync(d.S3Key) : null),
ImageUrl = d.S3Key != null ? _s3Service.GeneratePreSignedUrlAsync(d.S3Key) : null,
UploadedBy = d.UploadedBy?.ToBasicEmployeeVMFromEmployee() ?? uploadedBy?.ToBasicEmployeeVMFromEmployee(),
UploadedAt = d.UploadedAt,
Source = source,
ProjectId = projectId,
BuildingId = building?.Id,
BuildingName = building?.Name,
FloorIds = floor?.Id,
FloorName = floor?.FloorName,
WorkAreaId = workArea?.Id,
WorkAreaName = workArea?.AreaName,
TaskId = task?.Id,
ActivityName = workItem?.ActivityMaster?.ActivityName,
CommentId = comment?.Id,
Comment = comment?.Comment
};
}).ToList();
_logger.LogInfo("Image list fetched for ProjectId: {ProjectId}. Total documents: {Count}", projectId, documentVM.Count);
return Ok(ApiResponse<object>.SuccessResponse(documentVM, $"{documentVM.Count} image records fetched successfully", 200));
}
[HttpGet("batch/{batchId}")]
public async Task<IActionResult> GetImagesByBatch(Guid batchId)
{
_logger.LogInfo("GetImagesByBatch called for BatchId: {BatchId}", batchId);
// Step 1: Get the logged-in employee
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
// Step 2: Retrieve all documents in the batch
var documents = await _context.Documents
.Include(d => d.UploadedBy)
.Where(d => d.BatchId == batchId)
.ToListAsync();
if (!documents.Any())
{
_logger.LogWarning("No documents found for BatchId: {BatchId}", batchId);
return NotFound(ApiResponse<object>.ErrorResponse("No images found", "No images associated with this batch", 404));
}
var documentIds = documents.Select(d => d.Id).ToList();
// Step 3: Get task/comment reference IDs linked to these documents
var referenceIds = await _context.TaskAttachments
.Where(ta => documentIds.Contains(ta.DocumentId))
.Select(ta => ta.ReferenceId)
.Distinct()
.ToListAsync();
// Step 4: Try to identify the source of the attachment (task or comment)
var task = await _context.TaskAllocations
.Include(t => t.ReportedBy)
.FirstOrDefaultAsync(t => referenceIds.Contains(t.Id));
TaskComment? comment = null;
WorkItem? workItem = null;
Employee? uploadedBy = null;
string source = "";
if (task != null)
{
uploadedBy = task.ReportedBy;
workItem = await _context.WorkItems
.Include(wi => wi.ActivityMaster)
.FirstOrDefaultAsync(wi => wi.Id == task.WorkItemId);
source = "Report";
}
else
{
comment = await _context.TaskComments
.Include(tc => tc.TaskAllocation)
.Include(tc => tc.Employee)
.FirstOrDefaultAsync(tc => referenceIds.Contains(tc.Id));
var workItemId = comment?.TaskAllocation?.WorkItemId;
uploadedBy = comment?.Employee;
workItem = await _context.WorkItems
.Include(wi => wi.ActivityMaster)
.FirstOrDefaultAsync(wi => wi.Id == workItemId);
source = "Comment";
}
// Step 5: Traverse up to building level
var workAreaId = workItem?.WorkAreaId;
var workArea = await _context.WorkAreas
.Include(wa => wa.Floor)
.FirstOrDefaultAsync(wa => wa.Id == workAreaId);
var buildingId = workArea?.Floor?.BuildingId;
var building = await _context.Buildings
.FirstOrDefaultAsync(b => b.Id == buildingId);
// Step 6: Construct the response
var response = documents.Select(d => new
{
Id = d.Id,
BatchId = d.BatchId,
thumbnailUrl = d.ThumbS3Key != null
? _s3Service.GeneratePreSignedUrlAsync(d.ThumbS3Key)
: (d.S3Key != null ? _s3Service.GeneratePreSignedUrlAsync(d.S3Key) : null),
ImageUrl = d.S3Key != null ? _s3Service.GeneratePreSignedUrlAsync(d.S3Key) : null,
UploadedBy = d.UploadedBy?.ToBasicEmployeeVMFromEmployee() ?? uploadedBy?.ToBasicEmployeeVMFromEmployee(),
UploadedAt = d.UploadedAt,
Source = source,
ProjectId = building?.ProjectId,
BuildingId = building?.Id,
BuildingName = building?.Name,
FloorIds = workArea?.Floor?.Id,
FloorName = workArea?.Floor?.FloorName,
WorkAreaId = workArea?.Id,
WorkAreaName = workArea?.AreaName,
TaskId = task?.Id,
ActivityName = workItem?.ActivityMaster?.ActivityName,
CommentId = comment?.Id,
Comment = comment?.Comment
}).ToList();
_logger.LogInfo("Fetched {Count} image(s) for BatchId: {BatchId}", response.Count, batchId);
return Ok(ApiResponse<object>.SuccessResponse(response, "Images for provided batchId fetched successfully", 200));
}
[HttpGet("{documentId}")]
public async Task<IActionResult> GetImage(Guid documentId)
{
// Log the start of the image fetch process
_logger.LogInfo("GetImage called for DocumentId: {DocumentId}", documentId);
// Step 1: Get the currently logged-in employee (for future use like permission checks or auditing)
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
// Step 2: Fetch the document from the database based on the provided ID
var document = await _context.Documents.FirstOrDefaultAsync(d => d.Id == documentId);
// Step 3: If document doesn't exist, return a 400 Bad Request response
if (document == null)
{
_logger.LogWarning("Document not found for DocumentId: {DocumentId}", documentId);
return BadRequest(ApiResponse<object>.ErrorResponse("Document not found", "Document not found", 400));
}
// Step 4: Generate pre-signed URLs for thumbnail and full image (if keys exist)
string? thumbnailUrl = document.ThumbS3Key != null
? _s3Service.GeneratePreSignedUrlAsync(document.ThumbS3Key)
: null;
string? imageUrl = document.S3Key != null
? _s3Service.GeneratePreSignedUrlAsync(document.S3Key)
: null;
// Step 5: Prepare the response object
var response = new
{
ThumbnailUrl = thumbnailUrl,
ImageUrl = imageUrl
};
// Step 6: Log successful fetch and return the result
_logger.LogInfo("Image fetched successfully for DocumentId: {DocumentId}", documentId);
return Ok(ApiResponse<object>.SuccessResponse(response, "Image fetched successfully", 200));
}
}
}