using Knot.Contracts.Settings.Application.Abstractions; using Knot.Contracts.Settings.Application.DTOs; using Knot.Modules.Admin.Application.Admin.Commands; using Knot.Modules.Admin.Application.Admin.Commands.TestKlipy; using Knot.Modules.Admin.Application.Admin.Queries; using MediatR; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Routing; namespace Knot.Host.Presentation.Endpoints; public record KlipyTestDto(string ApiKey, string AppName); public record ResetPasswordRequest(string NewPassword); public static class AdminEndpoints { public static void MapAdminEndpoints(this WebApplication app) { var group = app.MapGroup("api/admin"); // Middleware handles auth group.MapGet("settings", async (ISender sender, CancellationToken ct) => { var result = await sender.Send(new GetSettingsQuery(), ct); return Results.Ok(result.Value); }); group.MapPut("settings", async ([FromBody] SystemSettingsDto settings, ISender sender, CancellationToken ct) => { var result = await sender.Send(new UpdateSettingsCommand(settings), ct); return Results.Ok(result.Value); }); group.MapPost("settings/test-klipy", async ([FromBody] KlipyTestDto dto, ISender sender, CancellationToken ct) => { var result = await sender.Send(new TestKlipyConnectionCommand(dto.ApiKey, dto.AppName), ct); return result.IsSuccess ? Results.Ok(new { success = true }) : Results.BadRequest(new { error = result.Error.Description }); }); group.MapGet("dashboard", async (ISender sender, CancellationToken ct) => { var result = await sender.Send(new GetDashboardStatsQuery(), ct); return Results.Ok(result.Value); }); group.MapPost("users/{userId:guid}/reset-password", async ([FromRoute] Guid userId, [FromBody] ResetPasswordRequest dto, ISender sender, CancellationToken ct) => { var result = await sender.Send(new ResetUserPasswordCommand(userId, dto.NewPassword), ct); if (result.IsFailure) { if (result.Error.Code == "User.NotFound") return Results.NotFound(new { error = "User not found" }); return Results.BadRequest(new { error = result.Error.Description }); } return Results.Ok(result.Value); }); group.MapPost("users/{userId:guid}/ban", async ([FromRoute] Guid userId, ISender sender, CancellationToken ct) => { var result = await sender.Send(new BanUserCommand(userId), ct); return result.IsSuccess ? Results.Ok() : Results.BadRequest(new { error = result.Error.Description }); }); group.MapPost("users/{userId:guid}/unban", async ([FromRoute] Guid userId, ISender sender, CancellationToken ct) => { var result = await sender.Send(new UnbanUserCommand(userId), ct); return result.IsSuccess ? Results.Ok() : Results.BadRequest(new { error = result.Error.Description }); }); group.MapGet("users", async (ISender sender, [FromQuery] string? query, CancellationToken ct = default) => { // При пустом или отсутствующем query возвращаем всех пользователей var searchQuery = string.IsNullOrWhiteSpace(query) ? "" : query; try { var result = await sender.Send(new SearchUsersQuery(searchQuery), ct); if (!result.IsSuccess) { Console.WriteLine($"[Admin] SearchUsers error: {result.Error.Code} - {result.Error.Description}"); return Results.BadRequest(new { error = result.Error.Description }); } return Results.Ok(result.Value); } catch (Exception ex) { Console.WriteLine($"[Admin] SearchUsers exception: {ex}"); return Results.BadRequest(new { error = ex.Message }); } }); group.MapGet("users/{id:guid}", async ([FromRoute] Guid id, ISender sender, CancellationToken ct) => { var result = await sender.Send(new GetUserDetailsQuery(id), ct); return result.IsSuccess ? Results.Ok(result.Value) : Results.NotFound("User not found"); }); group.MapGet("clean/dry-run", async (ISender sender, CancellationToken ct) => { var result = await sender.Send(new CleanDryRunQuery(), ct); if (!result.IsSuccess) { Console.WriteLine($"[Admin] Cleanup Dry Run Error: {result.Error.Description}"); return Results.BadRequest(new { error = result.Error.Description }); } return Results.Ok(result.Value); }); group.MapGet("timezones", () => { // Получаем все системные часовые пояса и формируем удобный для фронтенда формат var zones = TimeZoneInfo.GetSystemTimeZones() .Select(tz => new { id = tz.Id, displayName = tz.DisplayName, standardName = tz.StandardName, offsetMinutes = tz.BaseUtcOffset.TotalMinutes, offsetString = (tz.BaseUtcOffset >= TimeSpan.Zero ? "+" : "-") + tz.BaseUtcOffset.ToString(@"hh\:mm") }) .OrderBy(tz => tz.offsetMinutes); return Results.Ok(zones); }); } }