using MediatR; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Routing; using Nashel.Modules.Catalog.Application.Commands; using Nashel.Modules.Catalog.Application.Queries; namespace Nashel.Modules.Catalog.Presentation.Endpoints; /// /// API Каталога (Категории и Услуги). /// public static class CatalogEndpoints { public static void MapCatalogEndpoints(this IEndpointRouteBuilder app) { var catalogGroup = app.MapGroup("/api/catalog").WithTags("Catalog"); // --- Категории --- catalogGroup.MapPost("/categories", async ([FromBody] CreateCategoryCommand command, ISender sender) => { var result = await sender.Send(command); return result.IsSuccess ? Results.Ok(result.Value) : Results.BadRequest(result.Error); }) .WithName("CreateCategory") .WithOpenApi(operation => new(operation) { Summary = "Создать категорию (Admin)", Description = "Создает новую категорию. Требуются права администратора." }); catalogGroup.MapGet("/categories", async (ISender sender) => { var result = await sender.Send(new GetCategoriesQuery()); return result.IsSuccess ? Results.Ok(result.Value) : Results.BadRequest(result.Error); }) .WithName("GetCategories") .WithOpenApi(operation => new(operation) { Summary = "Получить все категории", Description = "Возвращает плоский список категорий с указанием ParentId для иерархии." }); // --- Услуги (Offers) --- var protectedOffersGroup = catalogGroup.MapGroup("").RequireAuthorization(); protectedOffersGroup.MapGet("/offers/my", async (ISender sender) => { var result = await sender.Send(new GetMyOffersQuery()); return result.IsSuccess ? Results.Ok(result.Value) : Results.BadRequest(result.Error); }) .WithName("GetMyOffers") .WithOpenApi(operation => new(operation) { Summary = "Получить мои услуги", Description = "Возвращает список услуг текущего пользователя." }); protectedOffersGroup.MapPost("/offers", async ([FromBody] CreateOfferCommand command, ISender sender) => { var result = await sender.Send(command); return result.IsSuccess ? Results.Ok(result.Value) : Results.BadRequest(result.Error); }) .WithName("CreateOffer") .WithOpenApi(operation => new(operation) { Summary = "Создать оффер/услугу", Description = "Создает новое предложение услуги в указанной категории." }); catalogGroup.MapGet("/offers/{id:guid}", async (Guid id, ISender sender) => { var result = await sender.Send(new GetOfferByIdQuery(id)); return result.IsSuccess && result.Value is not null ? Results.Ok(result.Value) : Results.NotFound(); }) .WithName("GetOfferById") .WithOpenApi(operation => new(operation) { Summary = "Получить детали услуги по ID", Description = "Возвращает полную информацию об услуге." }); protectedOffersGroup.MapPut("/offers/{id:guid}", async (Guid id, [FromBody] UpdateOfferPayload payload, ISender sender) => { var command = new UpdateOfferCommand(id, payload.Title, payload.Description, payload.Price.Amount, payload.Price.Type, payload.Attributes, payload.Images); await sender.Send(command); return Results.NoContent(); }) .WithName("UpdateOffer") .WithOpenApi(operation => new(operation) { Summary = "Обновить услугу" }); protectedOffersGroup.MapPatch("/offers/{id:guid}/toggle", async (Guid id, ISender sender) => { var isActive = await sender.Send(new ToggleOfferStatusCommand(id)); return Results.Ok(new { IsActive = isActive }); }) .WithName("ToggleOfferStatus") .WithOpenApi(operation => new(operation) { Summary = "Приостановить/активировать услугу" }); protectedOffersGroup.MapDelete("/offers/{id:guid}", async (Guid id, ISender sender) => { await sender.Send(new DeleteOfferCommand(id)); return Results.NoContent(); }) .WithName("DeleteOffer") .WithOpenApi(operation => new(operation) { Summary = "Удалить услугу" }); protectedOffersGroup.MapPost("/offers/image", async (IFormFile file, ISender sender) => { using var ms = new MemoryStream(); await file.CopyToAsync(ms); var url = await sender.Send(new UploadOfferImageCommand(ms.ToArray(), file.FileName)); return Results.Ok(new { url }); }) .WithName("UploadOfferImage") .WithOpenApi(operation => new(operation) { Summary = "Загрузить изображение для услуги" }) .DisableAntiforgery(); } } public record UpdateOfferPayload( string Title, string Description, PricePayload Price, Dictionary? Attributes, List? Images); public record PricePayload(decimal Amount, int Type);