using FluentValidation; using Microsoft.AspNetCore.Mvc; using Meezi.API.Models.Crm; using Meezi.API.Services; using Meezi.Core.Interfaces; using Meezi.Shared; namespace Meezi.API.Controllers; /// /// Marketing SMS — bring-your-own-provider. Each café configures its OWN /// Kavenegar API key + sender line; the platform does not sell SMS. /// [Route("api/cafes/{cafeId}/sms")] public class SmsController : CafeApiControllerBase { private readonly ISmsMarketingService _smsMarketingService; private readonly IValidator _campaignValidator; public SmsController( ISmsMarketingService smsMarketingService, IValidator campaignValidator) { _smsMarketingService = smsMarketingService; _campaignValidator = campaignValidator; } [HttpGet("settings")] public async Task GetSettings(string cafeId, ITenantContext tenant, CancellationToken cancellationToken) { if (EnsureCafeAccess(cafeId, tenant) is { } denied) return denied; if (EnsureManager(tenant) is { } forbidden) return forbidden; var data = await _smsMarketingService.GetSettingsAsync(cafeId, cancellationToken); return Ok(new ApiResponse(true, data)); } [HttpPut("settings")] public async Task UpdateSettings( string cafeId, [FromBody] UpdateSmsSettingsRequest request, ITenantContext tenant, CancellationToken cancellationToken) { if (EnsureCafeAccess(cafeId, tenant) is { } denied) return denied; if (EnsureManager(tenant) is { } forbidden) return forbidden; var (success, data, code, message) = await _smsMarketingService.UpdateSettingsAsync( cafeId, request, cancellationToken); if (!success) { return code switch { "NOT_FOUND" => NotFound(new ApiResponse(false, null, new ApiError(code, message!))), _ => BadRequest(new ApiResponse(false, null, new ApiError(code!, message!))) }; } return Ok(new ApiResponse(true, data)); } [HttpGet("balance")] public async Task GetBalance(string cafeId, ITenantContext tenant, CancellationToken cancellationToken) { if (EnsureCafeAccess(cafeId, tenant) is { } denied) return denied; var dto = await _smsMarketingService.GetBalanceAsync(cafeId, cancellationToken); return Ok(new ApiResponse(true, dto)); } [HttpGet("usage")] public async Task GetUsage(string cafeId, ITenantContext tenant, CancellationToken cancellationToken) { if (EnsureCafeAccess(cafeId, tenant) is { } denied) return denied; var data = await _smsMarketingService.GetUsageAsync(cafeId, cancellationToken); return Ok(new ApiResponse(true, data)); } [HttpPost("campaign")] public async Task SendCampaign( string cafeId, [FromBody] SendSmsCampaignRequest request, ITenantContext tenant, CancellationToken cancellationToken) { if (EnsureCafeAccess(cafeId, tenant) is { } denied) return denied; var validation = await _campaignValidator.ValidateAsync(request, cancellationToken); if (!validation.IsValid) return BadRequest(ValidationError(validation)); var (success, data, code, message) = await _smsMarketingService.SendCampaignAsync( cafeId, request, cancellationToken); if (!success) { return code switch { "SMS_NOT_CONFIGURED" => BadRequest( new ApiResponse(false, null, new ApiError(code, message!))), "NOT_FOUND" => NotFound(new ApiResponse(false, null, new ApiError(code, message!))), _ => BadRequest(new ApiResponse(false, null, new ApiError(code!, message!))) }; } return Ok(new ApiResponse(true, data)); } }