using AutoMapper; using Mws.Application.Common; using Mws.Domain.Tasks; using TaskPriority = Mws.Domain.Tasks.TaskPriority; using TaskStatus = Mws.Domain.Tasks.TaskStatus; namespace Mws.Application.Tasks; public class TaskService(IUnitOfWork uow, IMapper mapper) : ITaskService { public async Task> GetTasksAsync(Guid userId, Guid projectId, TaskStatus? status, TaskPriority? priority, Guid? assigneeId, CancellationToken ct = default) { await EnsureMemberAccessAsync(userId, projectId, ct); var tasks = await uow.Tasks.GetForProjectAsync(projectId, status, priority, assigneeId, ct); return mapper.Map>(tasks); } public async Task GetAsync(Guid userId, Guid taskId, CancellationToken ct = default) { var task = await GetTaskForUserAsync(userId, taskId, ct); return await GetDtoAsync(task.Id, ct); } public async Task CreateAsync(Guid userId, Guid projectId, CreateTaskRequest request, CancellationToken ct = default) { await EnsureMemberAccessAsync(userId, projectId, ct); if (string.IsNullOrWhiteSpace(request.Title)) { throw new BadRequestException("Task title is required"); } if (request.AssigneeId.HasValue && !await IsMemberAsync(request.AssigneeId.Value, projectId, ct)) { throw new BadRequestException("Assignee must be a member of the project"); } var now = DateTime.UtcNow; var task = new TaskItem { Id = Guid.NewGuid(), ProjectId = projectId, Title = request.Title.Trim(), Description = request.Description, Status = request.Status ?? TaskStatus.Todo, Priority = request.Priority ?? TaskPriority.Medium, AssigneeId = request.AssigneeId, DueDate = request.DueDate, CreatedBy = userId, CreatedAt = now, UpdatedAt = now, }; uow.Tasks.Add(task); await uow.SaveChangesAsync(ct); return await GetDtoAsync(task.Id, ct); } public async Task UpdateAsync(Guid userId, Guid taskId, UpdateTaskRequest request, CancellationToken ct = default) { var task = await GetTaskForUserAsync(userId, taskId, ct); if (string.IsNullOrWhiteSpace(request.Title)) { throw new BadRequestException("Task title is required"); } if (request.AssigneeId.HasValue && !await IsMemberAsync(request.AssigneeId.Value, task.ProjectId, ct)) { throw new BadRequestException("Assignee must be a member of the project"); } task.Title = request.Title.Trim(); task.Description = request.Description; task.Status = request.Status; task.Priority = request.Priority; task.AssigneeId = request.AssigneeId; task.DueDate = request.DueDate; task.UpdatedAt = DateTime.UtcNow; await uow.SaveChangesAsync(ct); return await GetDtoAsync(task.Id, ct); } public async Task DeleteAsync(Guid userId, Guid taskId, CancellationToken ct = default) { var task = await GetTaskForUserAsync(userId, taskId, ct); uow.Tasks.Remove(task); await uow.SaveChangesAsync(ct); } public async Task> SearchAsync(Guid userId, string term, CancellationToken ct = default) { var projectIds = await uow.Projects.GetProjectIdsForUserAsync(userId, ct); var tasks = await uow.Tasks.SearchWithAssigneeAsync(projectIds, term, 20, ct); return mapper.Map>(tasks); } private async Task GetDtoAsync(Guid id, CancellationToken ct) { var task = await uow.Tasks.GetWithAssigneeAsync(id, ct) ?? throw new NotFoundException("Task not found"); return mapper.Map(task); } private async Task GetTaskForUserAsync(Guid userId, Guid taskId, CancellationToken ct = default) { var task = await uow.Tasks.GetByIdAsync(taskId, ct) ?? throw new NotFoundException("Task not found"); if (!await IsMemberAsync(userId, task.ProjectId, ct)) { throw new NotFoundException("Task not found"); } return task; } private async Task EnsureMemberAccessAsync(Guid userId, Guid projectId, CancellationToken ct = default) { if (!await IsMemberAsync(userId, projectId, ct)) { throw new NotFoundException("Project not found"); } } private Task IsMemberAsync(Guid userId, Guid projectId, CancellationToken ct = default) => uow.Projects.IsMemberAsync(projectId, userId, ct); }