Spaces:
Running
Running
User
fix: adjust overall structure, clean out unnecessary fields, adjust table columns and layouts on task list and issue list
ecb51fa | using Microsoft.EntityFrameworkCore; | |
| using System; | |
| using System.Collections.Generic; | |
| using System.Linq; | |
| using System.Threading.Tasks; | |
| using TaskTrackingSystem.Database; | |
| using TaskTrackingSystem.Database.AppDbContextModels; | |
| using TaskTrackingSystem.Shared; | |
| using TaskTrackingSystem.Shared.Enums; | |
| using TaskTrackingSystem.Shared.Models.Task; | |
| using TaskTrackingSystem.WebApi.Infrastructure; | |
| namespace TaskTrackingSystem.WebApi.Features.Task | |
| { | |
| public class TaskService | |
| { | |
| private readonly AppDbContext _db; | |
| private readonly TaskTrackingSystem.WebApi.Features.Notification.FirebaseNotificationService _notificationService; | |
| public TaskService(AppDbContext db, TaskTrackingSystem.WebApi.Features.Notification.FirebaseNotificationService notificationService) | |
| { | |
| _db = db; | |
| _notificationService = notificationService; | |
| } | |
| private static DateTime? GetCompletedAt(TaskTrackingSystem.Database.AppDbContextModels.Task task) | |
| { | |
| return task.StatusId == AppTaskStatus.Done | |
| ? task.UpdatedAt ?? task.CreatedAt | |
| : null; | |
| } | |
| public async Task<IEnumerable<TaskDto>> GetAllTasksAsync(long roleId, long currentUserId) | |
| { | |
| var query = await BuildAccessibleTaskQueryAsync(roleId, currentUserId); | |
| return await query | |
| .OrderBy(t => t.DueDate) | |
| .ThenByDescending(t => t.CreatedAt ?? DateTime.UtcNow) | |
| .Select(t => new TaskDto | |
| { | |
| Id = t.Id, | |
| Title = t.Title, | |
| Description = t.Description, | |
| ProjectId = t.ProjectId, | |
| StatusId = t.StatusId, | |
| PriorityId = t.PriorityId, | |
| AssignedTo = t.AssignedTo, | |
| AssignedBy = t.AssignedBy, | |
| DueDate = t.DueDate, | |
| CreatedAt = t.CreatedAt ?? DateTime.UtcNow, | |
| IsArchived = t.IsArchived, | |
| CompletedAt = GetCompletedAt(t) | |
| }) | |
| .ToListAsync(); | |
| } | |
| public async Task<PagedResult<TaskDto>> GetPagedTasksAsync( | |
| long roleId, | |
| long currentUserId, | |
| string? search, | |
| long? projectId, | |
| AppTaskStatus? statusId, | |
| TaskPriority? priorityId, | |
| bool assignedOnly, | |
| int page, | |
| int pageSize) | |
| { | |
| var query = await BuildAccessibleTaskQueryAsync(roleId, currentUserId); | |
| if (assignedOnly) | |
| { | |
| query = query.Where(t => t.AssignedTo == currentUserId); | |
| } | |
| if (!string.IsNullOrWhiteSpace(search)) | |
| { | |
| var searchTerm = search.Trim().ToLower(); | |
| query = query.Where(t => | |
| (t.Title != null && t.Title.ToLower().Contains(searchTerm)) || | |
| (t.Description != null && t.Description.ToLower().Contains(searchTerm))); | |
| } | |
| if (projectId.HasValue && projectId.Value > 0) | |
| { | |
| query = query.Where(t => t.ProjectId == projectId.Value); | |
| } | |
| if (statusId.HasValue) | |
| { | |
| query = query.Where(t => t.StatusId == statusId.Value); | |
| } | |
| if (priorityId.HasValue) | |
| { | |
| query = query.Where(t => t.PriorityId == priorityId.Value); | |
| } | |
| return await query | |
| .OrderBy(t => t.DueDate) | |
| .ThenByDescending(t => t.CreatedAt ?? DateTime.UtcNow) | |
| .Select(t => new TaskDto | |
| { | |
| Id = t.Id, | |
| Title = t.Title, | |
| Description = t.Description, | |
| ProjectId = t.ProjectId, | |
| StatusId = t.StatusId, | |
| PriorityId = t.PriorityId, | |
| AssignedTo = t.AssignedTo, | |
| AssignedBy = t.AssignedBy, | |
| DueDate = t.DueDate, | |
| CreatedAt = t.CreatedAt ?? DateTime.UtcNow, | |
| IsArchived = t.IsArchived, | |
| CompletedAt = GetCompletedAt(t) | |
| }) | |
| .ToPagedResultAsync(page, pageSize); | |
| } | |
| public async Task<IEnumerable<TaskDto>> GetArchivedTasksAsync(long roleId, long currentUserId) | |
| { | |
| var query = await BuildAccessibleTaskQueryAsync(roleId, currentUserId, includeArchived: true); | |
| return await query | |
| .Where(t => t.IsArchived) | |
| .OrderByDescending(t => t.UpdatedAt ?? t.CreatedAt ?? DateTime.UtcNow) | |
| .Select(t => new TaskDto | |
| { | |
| Id = t.Id, | |
| Title = t.Title, | |
| Description = t.Description, | |
| ProjectId = t.ProjectId, | |
| StatusId = t.StatusId, | |
| PriorityId = t.PriorityId, | |
| AssignedTo = t.AssignedTo, | |
| AssignedBy = t.AssignedBy, | |
| DueDate = t.DueDate, | |
| CreatedAt = t.CreatedAt ?? DateTime.UtcNow, | |
| IsArchived = t.IsArchived, | |
| CompletedAt = GetCompletedAt(t) | |
| }) | |
| .ToListAsync(); | |
| } | |
| public async Task<TaskDto?> GetTaskByIdAsync(long id, long roleId, long currentUserId) | |
| { | |
| var query = await BuildAccessibleTaskQueryAsync(roleId, currentUserId); | |
| var task = await query.FirstOrDefaultAsync(t => t.Id == id); | |
| if (task == null) | |
| { | |
| return null; | |
| } | |
| return new TaskDto | |
| { | |
| Id = task.Id, | |
| Title = task.Title, | |
| Description = task.Description, | |
| ProjectId = task.ProjectId, | |
| StatusId = task.StatusId, | |
| PriorityId = task.PriorityId, | |
| AssignedTo = task.AssignedTo, | |
| AssignedBy = task.AssignedBy, | |
| DueDate = task.DueDate, | |
| CreatedAt = task.CreatedAt ?? DateTime.UtcNow, | |
| IsArchived = task.IsArchived, | |
| CompletedAt = GetCompletedAt(task) | |
| }; | |
| } | |
| public async Task<Result<TaskDto>> CreateTaskAsync(CreateTaskDto dto, long roleId, long currentUserId) | |
| { | |
| if (string.IsNullOrWhiteSpace(dto.Title)) | |
| { | |
| return Result<TaskDto>.Failure(ResultMessages.TaskTitleRequired, 400); | |
| } | |
| if (dto.ProjectId == 0) | |
| { | |
| return Result<TaskDto>.Failure(ResultMessages.SelectProjectRequired, 400); | |
| } | |
| var projectExists = await _db.Projects.AnyAsync(p => p.Id == dto.ProjectId && p.IsDeleted != true); | |
| if (!projectExists) | |
| { | |
| return Result<TaskDto>.Failure(ResultMessages.ProjectNotFound(dto.ProjectId), 404); | |
| } | |
| if (!await CanAccessProjectAsync(dto.ProjectId, roleId, currentUserId)) | |
| { | |
| return Result<TaskDto>.Failure("You do not have access to this project.", 403); | |
| } | |
| if (dto.AssignedTo.HasValue && !await IsProjectMemberAsync(dto.ProjectId, dto.AssignedTo.Value)) | |
| { | |
| return Result<TaskDto>.Failure("Task assignee must belong to the selected project.", 400); | |
| } | |
| var task = new TaskTrackingSystem.Database.AppDbContextModels.Task | |
| { | |
| Title = dto.Title, | |
| Description = dto.Description, | |
| ProjectId = dto.ProjectId, | |
| StatusId = dto.StatusId == 0 ? AppTaskStatus.Todo : dto.StatusId, | |
| PriorityId = dto.PriorityId == 0 ? TaskPriority.Medium : dto.PriorityId, | |
| AssignedTo = dto.AssignedTo, | |
| AssignedBy = dto.AssignedBy, | |
| DueDate = dto.DueDate, | |
| IsDeleted = false, | |
| IsArchived = false, | |
| CreatedAt = DateTime.UtcNow, | |
| CreatedBy = currentUserId | |
| }; | |
| _db.Tasks.Add(task); | |
| await _db.SaveChangesAsync(); | |
| if (task.AssignedTo.HasValue) | |
| { | |
| await _notificationService.NotifyTaskAssignedAsync(task, currentUserId); | |
| } | |
| var resultDto = new TaskDto | |
| { | |
| Id = task.Id, | |
| Title = task.Title, | |
| Description = task.Description, | |
| ProjectId = task.ProjectId, | |
| StatusId = task.StatusId, | |
| PriorityId = task.PriorityId, | |
| AssignedTo = task.AssignedTo, | |
| AssignedBy = task.AssignedBy, | |
| DueDate = task.DueDate, | |
| CreatedAt = task.CreatedAt ?? DateTime.UtcNow, | |
| CompletedAt = null, | |
| IsArchived = task.IsArchived | |
| }; | |
| return Result<TaskDto>.Success(resultDto, 201); | |
| } | |
| public async Task<Result> UpdateTaskAsync(long id, UpdateTaskDto dto, long? currentUserId = null) | |
| { | |
| if (string.IsNullOrWhiteSpace(dto.Title)) | |
| { | |
| return Result.Failure(ResultMessages.TaskTitleRequired, 400); | |
| } | |
| var task = await _db.Tasks.FirstOrDefaultAsync(t => t.Id == id && t.IsDeleted != true && !t.IsArchived); | |
| if (task == null) | |
| { | |
| return Result.Failure(ResultMessages.TaskNotFound(id), 404); | |
| } | |
| var previousStatusId = task.StatusId; | |
| var previousAssignedTo = task.AssignedTo; | |
| if (dto.AssignedTo.HasValue && !await IsProjectMemberAsync(task.ProjectId, dto.AssignedTo.Value)) | |
| { | |
| return Result.Failure("Task assignee must belong to the selected project.", 400); | |
| } | |
| task.Title = dto.Title; | |
| task.Description = dto.Description; | |
| task.StatusId = dto.StatusId; | |
| task.PriorityId = dto.PriorityId; | |
| task.AssignedTo = dto.AssignedTo; | |
| task.AssignedBy = dto.AssignedBy; | |
| task.DueDate = dto.DueDate; | |
| task.UpdatedAt = DateTime.UtcNow; | |
| task.UpdatedBy = currentUserId; | |
| _db.Tasks.Update(task); | |
| await _db.SaveChangesAsync(); | |
| if (previousAssignedTo != task.AssignedTo && task.AssignedTo.HasValue && currentUserId.HasValue) | |
| { | |
| await _notificationService.NotifyTaskAssignedAsync(task, currentUserId.Value); | |
| } | |
| if (previousStatusId != dto.StatusId && currentUserId.HasValue) | |
| { | |
| await _notificationService.NotifyTaskStatusChangedAsync(task, currentUserId.Value, previousStatusId, dto.StatusId); | |
| } | |
| return Result.Success(200); | |
| } | |
| public async Task<Result> SoftDeleteTaskAsync(long id, long currentUserId) | |
| { | |
| var task = await _db.Tasks.FirstOrDefaultAsync(t => t.Id == id && t.IsDeleted != true && !t.IsArchived); | |
| if (task == null) | |
| { | |
| return Result.Failure(ResultMessages.TaskNotFound(id), 404); | |
| } | |
| task.IsDeleted = true; | |
| task.UpdatedAt = DateTime.UtcNow; | |
| task.UpdatedBy = currentUserId; | |
| _db.Tasks.Update(task); | |
| await _db.SaveChangesAsync(); | |
| return Result.Success(200); | |
| } | |
| public async Task<Result<IEnumerable<TaskDto>>> GetTasksByUserIdAsync(long userId, long roleId, long currentUserId) | |
| { | |
| var userExists = await _db.Users.AnyAsync(u => u.Id == userId && !u.IsDeleted); | |
| if (!userExists) | |
| { | |
| return Result<IEnumerable<TaskDto>>.Failure(ResultMessages.UserNotFound(userId), 404); | |
| } | |
| var query = await BuildAccessibleTaskQueryAsync(roleId, currentUserId); | |
| var tasks = await query | |
| .Where(t => t.AssignedTo == userId) | |
| .Select(t => new TaskDto | |
| { | |
| Id = t.Id, | |
| Title = t.Title, | |
| Description = t.Description, | |
| ProjectId = t.ProjectId, | |
| StatusId = t.StatusId, | |
| PriorityId = t.PriorityId, | |
| AssignedTo = t.AssignedTo, | |
| AssignedBy = t.AssignedBy, | |
| DueDate = t.DueDate, | |
| CreatedAt = t.CreatedAt ?? DateTime.UtcNow, | |
| IsArchived = t.IsArchived, | |
| CompletedAt = GetCompletedAt(t) | |
| }) | |
| .ToListAsync(); | |
| return Result<IEnumerable<TaskDto>>.Success(tasks); | |
| } | |
| public async Task<Result> UpdateTaskStatusAsync(long id, AppTaskStatus statusId, long roleId, long currentUserId) | |
| { | |
| var task = await _db.Tasks.FirstOrDefaultAsync(t => t.Id == id && t.IsDeleted != true && !t.IsArchived); | |
| if (task == null) | |
| { | |
| return Result.Failure(ResultMessages.TaskNotFound(id), 404); | |
| } | |
| var previousStatusId = task.StatusId; | |
| task.StatusId = statusId; | |
| task.UpdatedAt = DateTime.UtcNow; | |
| task.UpdatedBy = currentUserId; | |
| _db.Tasks.Update(task); | |
| await _db.SaveChangesAsync(); | |
| if (previousStatusId != statusId) | |
| { | |
| await _notificationService.NotifyTaskStatusChangedAsync(task, currentUserId, previousStatusId, statusId); | |
| } | |
| return Result.Success(200); | |
| } | |
| public async Task<Result<int>> ArchiveDoneTasksAsync(long? projectId, long roleId, long currentUserId) | |
| { | |
| if (projectId.HasValue && projectId.Value > 0) | |
| { | |
| var projectExists = await _db.Projects.AnyAsync(p => p.Id == projectId.Value && p.IsDeleted != true); | |
| if (!projectExists) | |
| { | |
| return Result<int>.Failure(ResultMessages.ProjectNotFound(projectId.Value), 404); | |
| } | |
| if (!await CanAccessProjectAsync(projectId.Value, roleId, currentUserId)) | |
| { | |
| return Result<int>.Failure("You do not have access to this project.", 403); | |
| } | |
| } | |
| var query = await BuildAccessibleTaskQueryAsync(roleId, currentUserId); | |
| var tasksToArchive = await query | |
| .Where(t => t.StatusId == AppTaskStatus.Done) | |
| .ToListAsync(); | |
| if (projectId.HasValue && projectId.Value > 0) | |
| { | |
| tasksToArchive = tasksToArchive.Where(t => t.ProjectId == projectId.Value).ToList(); | |
| } | |
| foreach (var task in tasksToArchive) | |
| { | |
| task.IsArchived = true; | |
| task.UpdatedAt = DateTime.UtcNow; | |
| task.UpdatedBy = currentUserId; | |
| } | |
| if (tasksToArchive.Count > 0) | |
| { | |
| _db.Tasks.UpdateRange(tasksToArchive); | |
| await _db.SaveChangesAsync(); | |
| } | |
| return Result<int>.Success(tasksToArchive.Count); | |
| } | |
| private async Task<IQueryable<TaskTrackingSystem.Database.AppDbContextModels.Task>> BuildAccessibleTaskQueryAsync(long roleId, long currentUserId, bool includeArchived = false) | |
| { | |
| var query = _db.Tasks.Where(t => t.IsDeleted != true && (includeArchived || !t.IsArchived)); | |
| var isAdmin = await DataScopeAuthorization.IsAdminScopeAsync(_db, roleId); | |
| if (isAdmin) | |
| { | |
| return query; | |
| } | |
| var isManager = await DataScopeAuthorization.IsManagerScopeAsync(_db, roleId); | |
| if (isManager) | |
| { | |
| return query.Where(t => | |
| t.AssignedTo == currentUserId || | |
| t.CreatedBy == currentUserId || | |
| t.Project.ProjectMembers.Any(pm => pm.UserId == currentUserId)); | |
| } | |
| return query.Where(t => | |
| t.AssignedTo == currentUserId || | |
| t.CreatedBy == currentUserId); | |
| } | |
| private async Task<bool> CanAccessProjectAsync(long projectId, long roleId, long currentUserId) | |
| { | |
| var isAdmin = await DataScopeAuthorization.IsAdminScopeAsync(_db, roleId); | |
| if (isAdmin) | |
| { | |
| return await _db.Projects.AnyAsync(p => p.Id == projectId && p.IsDeleted != true); | |
| } | |
| return await _db.Projects.AnyAsync(p => | |
| p.Id == projectId && | |
| p.IsDeleted != true && | |
| (p.CreatedById == currentUserId || p.ProjectMembers.Any(pm => pm.UserId == currentUserId))); | |
| } | |
| private async Task<bool> IsProjectMemberAsync(long projectId, long userId) | |
| { | |
| return await _db.ProjectMembers.AnyAsync(pm => pm.ProjectId == projectId && pm.UserId == userId); | |
| } | |
| } | |
| } | |