using JdeScoping.Api.Contracts.ManualSync; using JdeScoping.DataAccess.Services; using JdeScoping.DataSync.Services; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; namespace JdeScoping.Api.Controllers; /// /// API endpoints for manual data sync request management. /// [Route("api/manual-sync")] [ApiController] [Authorize] public class ManualSyncController : ApiControllerBase { private readonly IManualSyncRequestService _manualSyncRequestService; private readonly IPipelineRegistry _pipelineRegistry; /// /// Initializes a new instance of the class. /// /// The service for managing manual sync requests. /// The pipeline registry for querying pipeline information. public ManualSyncController( IManualSyncRequestService manualSyncRequestService, IPipelineRegistry pipelineRegistry) { _manualSyncRequestService = manualSyncRequestService; _pipelineRegistry = pipelineRegistry; } /// /// Gets all manual sync requests, optionally filtered to pending only. /// /// If true, returns only pending requests. Default is false. /// Cancellation token. /// A list of manual sync request view models. [HttpGet] [ProducesResponseType(typeof(List), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] public async Task>> GetRequests( [FromQuery] bool pendingOnly = false, CancellationToken ct = default) { var requests = await _manualSyncRequestService.GetRequestsAsync(pendingOnly, ct); var viewModels = requests.Select(r => new ManualSyncRequestViewModel { Id = r.Id, PipelineName = r.PipelineName, SyncType = r.SyncType, RequestDT = r.RequestDT, RequestedBy = r.RequestedBy, CompletedDT = r.CompletedDT, CancelDT = r.CancelDT, CancelledBy = r.CancelledBy, Status = r.Status, RowVersionBase64 = Convert.ToBase64String(r.RowVersion) }).ToList(); return Ok(viewModels); } /// /// Gets all available pipelines with their supported sync types. /// /// A list of pipeline information view models. [HttpGet("pipelines")] [ProducesResponseType(typeof(List), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] public ActionResult> GetPipelines() { var pipelines = _pipelineRegistry.GetEnabledPipelines() .Select(p => new PipelineInfoViewModel { Name = p.Name, SupportedSyncTypes = GetSupportedSyncTypes(p) }) .ToList(); return Ok(pipelines); } /// /// Creates a new manual sync request. /// /// The create request containing pipeline name and sync type. /// Cancellation token. /// The created manual sync request view model. [HttpPost] [ProducesResponseType(typeof(ManualSyncRequestViewModel), StatusCodes.Status201Created)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] public async Task> CreateRequest( [FromBody] CreateManualSyncRequestDto dto, CancellationToken ct = default) { var username = User.Identity?.Name; if (string.IsNullOrEmpty(username)) { return Unauthorized(); } // Validate pipeline and sync type combination using the registry if (!_pipelineRegistry.IsValidPipelineAndSyncType(dto.PipelineName, dto.SyncType)) { return BadRequest($"Invalid pipeline/sync type combination. Pipeline '{dto.PipelineName}' does not support sync type '{dto.SyncType}'."); } var request = await _manualSyncRequestService.CreateRequestAsync( dto.PipelineName, dto.SyncType, username, ct); var viewModel = new ManualSyncRequestViewModel { Id = request.Id, PipelineName = request.PipelineName, SyncType = request.SyncType, RequestDT = request.RequestDT, RequestedBy = request.RequestedBy, CompletedDT = request.CompletedDT, CancelDT = request.CancelDT, CancelledBy = request.CancelledBy, Status = request.Status, RowVersionBase64 = Convert.ToBase64String(request.RowVersion) }; return CreatedAtAction(nameof(GetRequests), viewModel); } /// /// Cancels a pending manual sync request. /// /// The ID of the request to cancel. /// The cancel request containing the row version for concurrency. /// Cancellation token. /// A success message if cancelled, or a conflict error if already processed. [HttpPost("{id:int}/cancel")] [ProducesResponseType(typeof(object), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status409Conflict)] public async Task CancelRequest( int id, [FromBody] CancelManualSyncRequestDto dto, CancellationToken ct = default) { var username = User.Identity?.Name; if (string.IsNullOrEmpty(username)) { return Unauthorized(); } byte[] rowVersion; try { rowVersion = Convert.FromBase64String(dto.RowVersionBase64); } catch (FormatException) { return BadRequest("Invalid RowVersionBase64 format."); } var cancelled = await _manualSyncRequestService.CancelRequestAsync( id, username, rowVersion, ct); if (!cancelled) { return Conflict(new { message = "The request has already been completed or cancelled, or the data has been modified by another user." }); } return Ok(new { message = "Request cancelled successfully." }); } private static List GetSupportedSyncTypes(DataSync.Configuration.EtlPipelineConfig pipeline) { var types = new List(); if (pipeline.SupportsMassSync) types.Add("mass"); if (pipeline.SupportsDailySync) types.Add("daily"); if (pipeline.SupportsHourlySync) types.Add("hourly"); return types; } }