using FluentValidation; using Microsoft.AspNetCore.Mvc; using Meezi.API.Models.Crm; using Meezi.API.Services; using Meezi.Core.Authorization; using Meezi.Core.Interfaces; using Meezi.Shared; namespace Meezi.API.Controllers; [Route("api/cafes/{cafeId}/customers")] public class CustomersController : CafeApiControllerBase { private readonly ICustomerService _customerService; private readonly IValidator _createValidator; private readonly IValidator _updateValidator; public CustomersController( ICustomerService customerService, IValidator createValidator, IValidator updateValidator) { _customerService = customerService; _createValidator = createValidator; _updateValidator = updateValidator; } [HttpGet] public async Task Search( string cafeId, [FromQuery] string? q, ITenantContext tenant, CancellationToken cancellationToken) { if (EnsureCafeAccess(cafeId, tenant) is { } denied) return denied; var data = await _customerService.SearchAsync(cafeId, q, cancellationToken); return Ok(new ApiResponse>(true, data)); } [HttpGet("{id}")] public async Task Get( string cafeId, string id, ITenantContext tenant, CancellationToken cancellationToken) { if (EnsureCafeAccess(cafeId, tenant) is { } denied) return denied; var data = await _customerService.GetAsync(cafeId, id, cancellationToken); if (data is null) return NotFoundError(); return Ok(new ApiResponse(true, data)); } [HttpPost] public async Task Create( string cafeId, [FromBody] CreateCustomerRequest request, ITenantContext tenant, CancellationToken cancellationToken) { if (EnsureCafeAccess(cafeId, tenant) is { } denied) return denied; if (EnsurePermission(tenant, Permission.CreateCustomer) is { } permDenied) return permDenied; var validation = await _createValidator.ValidateAsync(request, cancellationToken); if (!validation.IsValid) return BadRequest(ValidationError(validation)); var data = await _customerService.CreateAsync(cafeId, request, cancellationToken); if (data is null) return BadRequest(new ApiResponse(false, null, new ApiError("DUPLICATE_PHONE", "A customer with this phone already exists."))); return Ok(new ApiResponse(true, data)); } [HttpPatch("{id}")] public async Task Update( string cafeId, string id, [FromBody] UpdateCustomerRequest request, ITenantContext tenant, CancellationToken cancellationToken) { if (EnsureCafeAccess(cafeId, tenant) is { } denied) return denied; if (EnsurePermission(tenant, Permission.EditCustomer) is { } permDenied) return permDenied; var validation = await _updateValidator.ValidateAsync(request, cancellationToken); if (!validation.IsValid) return BadRequest(ValidationError(validation)); var existing = await _customerService.GetAsync(cafeId, id, cancellationToken); if (existing is null) return NotFoundError(); var data = await _customerService.UpdateAsync(cafeId, id, request, cancellationToken); if (data is null) return BadRequest(new ApiResponse(false, null, new ApiError("DUPLICATE_PHONE", "A customer with this phone already exists."))); return Ok(new ApiResponse(true, data)); } [HttpDelete("{id}")] public async Task Delete( string cafeId, string id, ITenantContext tenant, CancellationToken cancellationToken) { if (EnsureCafeAccess(cafeId, tenant) is { } denied) return denied; if (EnsurePermission(tenant, Permission.DeleteCustomer) is { } permDenied) return permDenied; var deleted = await _customerService.DeleteAsync(cafeId, id, cancellationToken); if (!deleted) return NotFoundError(); return Ok(new ApiResponse(true, new { id })); } }