using Api.SeaHavenIndustries.DTOs; using Api.SeaHavenIndustries.Helper; using Data.SeaHavenIndustries; using FluentValidation; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using SeaHaven.Services.DTOs; using SeaHaven.Services.Exceptions; using SeaHaven.Services.Interfaces; namespace Api.SeaHavenIndustries.Controllers { [Authorize] [ApiController] [Route("api/[controller]")] [Route("api/locations")] public class LocationController : Controller { private readonly ILocationService _locationService; private readonly ILogger _logger; public LocationController(ILocationService locationService, ILogger logger) { _locationService = locationService; _logger = logger; } [HttpGet("sites")] public async Task GetSites( string? search = null, CancellationToken cancellationToken = default) { var data = await _locationService.GetSiteOptionsAsync(search, cancellationToken); return Ok(data); } [HttpGet("GetLocationList")] public async Task GetLocationList(string? search = "", int page = 1, int pageSize = 10, string? states = null, string? sortBy = null, string? sortDirection = null, CancellationToken cancellationToken = default) { try { var pagedResult = await _locationService.GetLocationListPagedAsync(page, pageSize, search, states, cancellationToken, sortBy, sortDirection); var data = pagedResult.Items.Select(l => new { Id = l.Id, Name = l.Name, Title = l.Title, Address = l.Address1, Address1 = l.Address1, Address2 = l.Address2, City = l.City, State = l.State, ZipCode = l.Zip, Phone = l.PhoneNumber, Contact = l.Contacts?.FirstOrDefault()?.Name, ContactEmail = l.Email, Status = l.Status, AccountId = l.AccountId, ClientName = l.AccountName, AccountName = l.AccountName, Contacts = l.Contacts?.Select(c => new { c.Id, c.Name, c.Phone }).ToList() }); var viewModel = new Pagination_DTO { Data = data, PageNumber = page, PageSize = pageSize, TotalCount = pagedResult.TotalCount, TotalPages = (int)Math.Ceiling(pagedResult.TotalCount / (double)pageSize) }; return Ok(viewModel); } catch (ValidationException vex) { var errors = string.Join(", ", vex.Errors.Select(e => e.ErrorMessage)); return BadRequest(new Response { Status = "Validation Error", Message = errors }); } } [HttpGet("{id}")] public async Task GetLocationById(int id, CancellationToken cancellationToken) { var location = await _locationService.GetLocationDetailAsync(id, cancellationToken); if (location == null) return NotFound(new Response { Status = "Error", Message = "Location not found" }); var result = new { location.Id, location.Name, location.Title, Address = location.Address1, location.Address1, location.Address2, location.City, location.State, ZipCode = location.Zip, Phone = location.PhoneNumber, Contact = location.Contacts?.FirstOrDefault()?.Name, ContactEmail = location.Email, location.Status, location.AccountId, ClientName = location.AccountName, location.AccountName, location.Notes, Contacts = location.Contacts?.Select(c => new { c.Id, c.Name, c.Phone }).ToList() }; return Ok(result); } [HttpPost] public async Task AddLocation([FromBody] Location_DTO model, CancellationToken cancellationToken) { try { await _locationService.CreateLocationFromRequestAsync(MapToCreateRequest(model), User, cancellationToken); return Ok(new DataResponse { Message = "Location Created Successfully", Status = "200" }); } catch (SiteCodeConflictException) { return SiteCodeConflict(); } catch (ValidationException vex) { var errors = string.Join(", ", vex.Errors.Select(e => e.ErrorMessage)); return BadRequest(new Response { Status = "Validation Error", Message = errors }); } catch (UnauthorizedAccessException) { return StatusCode(StatusCodes.Status403Forbidden, new Response { Status = "Error", Message = "You are not allowed to assign this account." }); } catch (Exception ex) { return StatusCode(500, new Response { Status = "Error", Message = _logger.Sanitize(ex) }); } } [HttpPut("{id}")] public async Task EditLocation(int id, [FromBody] EditLocation_DTO model, CancellationToken cancellationToken) { try { await _locationService.UpdateLocationFromRequestAsync(id, MapToUpdateRequest(model), User, cancellationToken); return Ok(new DataResponse { Message = "Location Updated Successfully", Status = "200" }); } catch (SiteCodeConflictException) { return SiteCodeConflict(); } catch (ValidationException vex) { var errors = string.Join(", ", vex.Errors.Select(e => e.ErrorMessage)); return BadRequest(new Response { Status = "Validation Error", Message = errors }); } catch (UnauthorizedAccessException) { return StatusCode(StatusCodes.Status403Forbidden, new Response { Status = "Error", Message = "You are not allowed to assign this account." }); } catch (KeyNotFoundException) { return NotFound(new Response { Status = "Error", Message = "Location not found" }); } catch (Exception ex) { return StatusCode(500, new Response { Status = "Error", Message = _logger.Sanitize(ex) }); } } [HttpPatch("{id}/contact-info")] public async Task UpdateSiteContactInfo(int id, [FromBody] SiteContactInfoInput_DTO model, CancellationToken cancellationToken) { try { await _locationService.UpdateSiteContactInfoAsync( id, new SiteContactInfoRequestDTO { Contacts = MapSiteContacts(model.Contacts), Notes = model.Notes }, User, cancellationToken); return Ok(new DataResponse { Message = "Site Updated Successfully", Status = "200" }); } catch (ValidationException vex) { var errors = string.Join(", ", vex.Errors.Select(e => e.ErrorMessage)); return BadRequest(new Response { Status = "Validation Error", Message = errors }); } catch (UnauthorizedAccessException) { return StatusCode(StatusCodes.Status403Forbidden, new Response { Status = "Error", Message = "You are not allowed to edit this site." }); } catch (KeyNotFoundException) { return NotFound(new Response { Status = "Error", Message = "Location not found" }); } catch (Exception ex) { return StatusCode(500, new Response { Status = "Error", Message = _logger.Sanitize(ex) }); } } [HttpGet("{id}/open-work-orders")] public async Task GetOpenWorkOrders(int id, CancellationToken cancellationToken) { try { var result = await _locationService.GetOpenWorkOrdersAsync(id, User, cancellationToken); if (result == null) return NotFound(new Response { Status = "Error", Message = "Location not found" }); return Ok(result); } catch (UnauthorizedAccessException) { return StatusCode(StatusCodes.Status403Forbidden, new Response { Status = "Error", Message = "You are not allowed to view this site." }); } } [HttpDelete("{id}")] public async Task DeleteLocation(int id, CancellationToken cancellationToken) { try { var found = await _locationService.DeleteLocationByIdAsync(id, User, cancellationToken); if (!found) return NotFound(new Response { Status = "Error", Message = "Location not found" }); return Ok(new DataResponse { Message = "Location Deleted Successfully", Status = "200" }); } catch (SiteForbiddenException forbidden) { return StatusCode(StatusCodes.Status403Forbidden, new Response { Status = "Error", Message = forbidden.Message }); } catch (UnauthorizedAccessException) { return StatusCode(StatusCodes.Status403Forbidden, new Response { Status = "Error", Message = "You are not allowed to delete this site." }); } catch (KeyNotFoundException) { return NotFound(new Response { Status = "Error", Message = "Location not found" }); } catch (Exception ex) { return StatusCode(500, new Response { Status = "Error", Message = _logger.Sanitize(ex) }); } } [HttpGet("DeleteLocation")] public async Task DeleteLocationByQuery(int id, CancellationToken cancellationToken) { return await DeleteLocation(id, cancellationToken); } private ObjectResult SiteCodeConflict() => Conflict(new { Status = "Conflict", Message = SiteCodeConflictException.PublicMessage, Code = SiteCodeConflictException.ErrorCode }); private static LocationCreateRequestDTO MapToCreateRequest(Location_DTO model) { return new LocationCreateRequestDTO { Name = model.Name, Title = model.Title, Address = model.Address, City = model.City, State = model.State, ZipCode = model.ZipCode, Phone = model.Phone, ContactEmail = model.ContactEmail, Status = model.Status, AccountId = model.GetAccountId(), Notes = model.Notes, Contacts = MapSiteContacts(model.Contacts) }; } private static LocationUpdateRequestDTO MapToUpdateRequest(EditLocation_DTO model) { return new LocationUpdateRequestDTO { Name = model.Name, Title = model.Title, Address = model.Address, City = model.City, State = model.State, ZipCode = model.ZipCode, Phone = model.Phone, ContactEmail = model.ContactEmail, Status = model.Status, AccountId = model.GetAccountId(), Notes = model.Notes, Contacts = MapSiteContacts(model.Contacts) }; } private static List? MapSiteContacts(List? contacts) { if (contacts == null) return null; return contacts .Select(c => new SiteContactRequestDTO { Id = c.Id, Name = c.Name, Phone = c.Phone }) .ToList(); } } }