/
ivanstrike
/
tasker
Обзор
Документация
Войти
/
ivanstrike
/
tasker
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
feature/lab2
Controllers/Internal/InternalController.cs
276 строк
10 KB
ivanstrike
Лабораторная работа 2
08 ноя 2025, 11:16
08 ноя 2025, 11:16
ec39293
Код
Авторство
О чём код?
using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using TaskerApi.Data; using TaskStatus = TaskerApi.Models.TaskStatus; namespace TaskerApi.Controllers.Internal; /// <summary> /// Internal API для внутренних сервисов (аутентификация через API ключ) /// </summary> /// <remarks> /// Этот контроллер предназначен для внутренних микросервисов и не использует JWT аутентификацию. /// Вместо этого используется простая аутентификация через API ключ в заголовке X-Api-Key. /// /// Упрощения по сравнению с публичным API: /// - Нет проверки владельца задачи/проекта (доверенный сервис) /// - Нет пагинации (предполагается использование для небольших выборок) /// - Упрощенная структура ответов (только необходимые поля) /// - Нет rate limiting (внутренние сервисы под контролем) /// - Массовые операции для эффективности /// </remarks> [ApiController] [Route("api/internal")] [ApiExplorerSettings(GroupName = "internal")] public class InternalController : ControllerBase { private readonly ApplicationDbContext _context; private readonly ILogger<InternalController> _logger; public InternalController(ApplicationDbContext context, ILogger<InternalController> logger) { _context = context; _logger = logger; } /// <summary> /// [INTERNAL] Получить статистику по всем пользователям /// </summary> /// <remarks> /// Этот эндпоинт возвращает агрегированную статистику по всем пользователям системы. /// Используется для мониторинга и аналитики. /// </remarks> [HttpGet("statistics/global")] [ProducesResponseType(StatusCodes.Status200OK)] public async Task<ActionResult> GetGlobalStatistics() { var totalUsers = await _context.Users.CountAsync(); var totalProjects = await _context.Projects.CountAsync(); var totalTasks = await _context.Tasks.CountAsync(); var tasksByStatus = await _context.Tasks .GroupBy(t => t.Status) .Select(g => new { Status = g.Key.ToString(), Count = g.Count() }) .ToListAsync(); var projectsByUser = await _context.Projects .GroupBy(p => p.OwnerId) .Select(g => new { UserId = g.Key, ProjectCount = g.Count() }) .OrderByDescending(x => x.ProjectCount) .Take(10) .ToListAsync(); _logger.LogInformation("Global statistics requested"); return Ok(new { totalUsers, totalProjects, totalTasks, tasksByStatus, topProjectOwners = projectsByUser, timestamp = DateTime.UtcNow }); } /// <summary> /// [INTERNAL] Получить все задачи пользователя (без пагинации) /// </summary> /// <param name="userId">ID пользователя</param> [HttpGet("users/{userId}/tasks")] [ProducesResponseType(StatusCodes.Status200OK)] public async Task<ActionResult> GetUserTasks(Guid userId) { var tasks = await _context.Tasks .Where(t => t.AssignedToId == userId) .Select(t => new { t.Id, t.Title, t.Status, t.Priority, t.ProjectId, t.CreatedAt, t.DueDate }) .ToListAsync(); _logger.LogInformation("Internal API: Retrieved {Count} tasks for user {UserId}", tasks.Count, userId); return Ok(new { userId, taskCount = tasks.Count, tasks }); } /// <summary> /// [INTERNAL] Получить все проекты пользователя (без пагинации) /// </summary> /// <param name="userId">ID пользователя</param> [HttpGet("users/{userId}/projects")] [ProducesResponseType(StatusCodes.Status200OK)] public async Task<ActionResult> GetUserProjects(Guid userId) { var projects = await _context.Projects .Where(p => p.OwnerId == userId) .Select(p => new { p.Id, p.Name, p.IsArchived, p.CreatedAt, TaskCount = p.Tasks.Count }) .ToListAsync(); _logger.LogInformation("Internal API: Retrieved {Count} projects for user {UserId}", projects.Count, userId); return Ok(new { userId, projectCount = projects.Count, projects }); } /// <summary> /// [INTERNAL] Массовое обновление статуса задач /// </summary> /// <param name="request">Список ID задач и новый статус</param> /// <remarks> /// Позволяет обновить статус нескольких задач одним запросом. /// Используется для автоматических процессов (например, закрытие просроченных задач). /// </remarks> [HttpPost("tasks/bulk-update-status")] [ProducesResponseType(StatusCodes.Status200OK)] public async Task<ActionResult> BulkUpdateTaskStatus([FromBody] BulkUpdateStatusRequest request) { var tasks = await _context.Tasks .Where(t => request.TaskIds.Contains(t.Id)) .ToListAsync(); if (tasks.Count == 0) { return Ok(new { message = "No tasks found", updatedCount = 0 }); } foreach (var task in tasks) { task.Status = request.NewStatus; task.UpdatedAt = DateTime.UtcNow; if (request.NewStatus == TaskStatus.Completed && task.CompletedAt == null) { task.CompletedAt = DateTime.UtcNow; } } await _context.SaveChangesAsync(); _logger.LogInformation("Internal API: Bulk updated {Count} tasks to status {Status}", tasks.Count, request.NewStatus); return Ok(new { message = "Tasks updated successfully", updatedCount = tasks.Count, newStatus = request.NewStatus.ToString(), taskIds = tasks.Select(t => t.Id).ToList() }); } /// <summary> /// [INTERNAL] Очистка старых записей rate limiting /// </summary> /// <param name="olderThanHours">Удалить записи старше указанного количества часов</param> [HttpDelete("maintenance/cleanup-rate-limits")] [ProducesResponseType(StatusCodes.Status200OK)] public async Task<ActionResult> CleanupRateLimits([FromQuery] int olderThanHours = 24) { var cutoffDate = DateTime.UtcNow.AddHours(-olderThanHours); var oldRecords = await _context.RateLimitInfos .Where(r => r.LastRequestAt < cutoffDate) .ToListAsync(); _context.RateLimitInfos.RemoveRange(oldRecords); await _context.SaveChangesAsync(); _logger.LogInformation("Internal API: Cleaned up {Count} old rate limit records", oldRecords.Count); return Ok(new { message = "Cleanup completed", deletedCount = oldRecords.Count, cutoffDate }); } /// <summary> /// [INTERNAL] Очистка старых идемпотентных запросов /// </summary> /// <param name="olderThanHours">Удалить записи старше указанного количества часов</param> [HttpDelete("maintenance/cleanup-idempotent-requests")] [ProducesResponseType(StatusCodes.Status200OK)] public async Task<ActionResult> CleanupIdempotentRequests([FromQuery] int olderThanHours = 48) { var cutoffDate = DateTime.UtcNow.AddHours(-olderThanHours); var oldRecords = await _context.IdempotentRequests .Where(r => r.CreatedAt < cutoffDate) .ToListAsync(); _context.IdempotentRequests.RemoveRange(oldRecords); await _context.SaveChangesAsync(); _logger.LogInformation("Internal API: Cleaned up {Count} old idempotent request records", oldRecords.Count); return Ok(new { message = "Cleanup completed", deletedCount = oldRecords.Count, cutoffDate }); } /// <summary> /// [INTERNAL] Health check для внутренних сервисов /// </summary> [HttpGet("health")] [ProducesResponseType(StatusCodes.Status200OK)] public async Task<ActionResult> HealthCheck() { try { var canConnect = await _context.Database.CanConnectAsync(); return Ok(new { status = canConnect ? "healthy" : "unhealthy", timestamp = DateTime.UtcNow, database = canConnect ? "connected" : "disconnected" }); } catch (Exception ex) { _logger.LogError(ex, "Health check failed"); return Ok(new { status = "unhealthy", timestamp = DateTime.UtcNow, error = ex.Message }); } } } /// <summary> /// DTO для массового обновления статуса задач /// </summary> public class BulkUpdateStatusRequest { public List<Guid> TaskIds { get; set; } = new(); public TaskStatus NewStatus { get; set; } }