feat: Created task lists that work in categories
All checks were successful
Build and Push App Image / build-and-push (push) Successful in 1m20s
All checks were successful
Build and Push App Image / build-and-push (push) Successful in 1m20s
This commit is contained in:
@@ -15,6 +15,7 @@ import (
|
||||
// CategoryService handles category operations
|
||||
type CategoryService struct {
|
||||
categoryRepo repositories.CategoryRepository
|
||||
taskListRepo repositories.TaskListRepository
|
||||
membershipRepo repositories.MembershipRepository
|
||||
noteRepo repositories.NoteRepository
|
||||
permissionService *PermissionService
|
||||
@@ -23,12 +24,14 @@ type CategoryService struct {
|
||||
// NewCategoryService creates a new category service
|
||||
func NewCategoryService(
|
||||
categoryRepo repositories.CategoryRepository,
|
||||
taskListRepo repositories.TaskListRepository,
|
||||
membershipRepo repositories.MembershipRepository,
|
||||
noteRepo repositories.NoteRepository,
|
||||
permissionService *PermissionService,
|
||||
) *CategoryService {
|
||||
return &CategoryService{
|
||||
categoryRepo: categoryRepo,
|
||||
taskListRepo: taskListRepo,
|
||||
membershipRepo: membershipRepo,
|
||||
noteRepo: noteRepo,
|
||||
permissionService: permissionService,
|
||||
@@ -134,6 +137,14 @@ func (s *CategoryService) buildCategoryTree(ctx context.Context, category *entit
|
||||
}
|
||||
}
|
||||
|
||||
// Get task lists in this category
|
||||
taskLists, err := s.taskListRepo.ListTaskListsByCategory(ctx, spaceID, category.ID)
|
||||
if err == nil {
|
||||
for _, taskList := range taskLists {
|
||||
tree.TaskLists = append(tree.TaskLists, dto.NewTaskListDTO(taskList))
|
||||
}
|
||||
}
|
||||
|
||||
return tree, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ type SpaceService struct {
|
||||
membershipRepo repositories.MembershipRepository
|
||||
noteRepo repositories.NoteRepository
|
||||
categoryRepo repositories.CategoryRepository
|
||||
taskListRepo repositories.TaskListRepository
|
||||
userRepo repositories.UserRepository
|
||||
permissionService *PermissionService
|
||||
}
|
||||
@@ -27,6 +28,7 @@ func NewSpaceService(
|
||||
membershipRepo repositories.MembershipRepository,
|
||||
noteRepo repositories.NoteRepository,
|
||||
categoryRepo repositories.CategoryRepository,
|
||||
taskListRepo repositories.TaskListRepository,
|
||||
userRepo repositories.UserRepository,
|
||||
permissionService *PermissionService,
|
||||
) *SpaceService {
|
||||
@@ -35,6 +37,7 @@ func NewSpaceService(
|
||||
membershipRepo: membershipRepo,
|
||||
noteRepo: noteRepo,
|
||||
categoryRepo: categoryRepo,
|
||||
taskListRepo: taskListRepo,
|
||||
userRepo: userRepo,
|
||||
permissionService: permissionService,
|
||||
}
|
||||
@@ -180,6 +183,9 @@ func (s *SpaceService) DeleteSpace(ctx context.Context, spaceID, userID bson.Obj
|
||||
if err := s.categoryRepo.DeleteCategoriesBySpaceID(ctx, spaceID); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.taskListRepo.DeleteTaskListsBySpaceID(ctx, spaceID); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.membershipRepo.DeleteMembershipsBySpaceID(ctx, spaceID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
// TaskService handles task and task status operations.
|
||||
type TaskService struct {
|
||||
taskRepo repositories.TaskRepository
|
||||
taskListRepo repositories.TaskListRepository
|
||||
taskStatusRepo repositories.TaskStatusRepository
|
||||
noteRepo repositories.NoteRepository
|
||||
categoryRepo repositories.CategoryRepository
|
||||
@@ -27,6 +28,7 @@ type TaskService struct {
|
||||
// NewTaskService creates a task service.
|
||||
func NewTaskService(
|
||||
taskRepo repositories.TaskRepository,
|
||||
taskListRepo repositories.TaskListRepository,
|
||||
taskStatusRepo repositories.TaskStatusRepository,
|
||||
noteRepo repositories.NoteRepository,
|
||||
categoryRepo repositories.CategoryRepository,
|
||||
@@ -35,6 +37,7 @@ func NewTaskService(
|
||||
) *TaskService {
|
||||
return &TaskService{
|
||||
taskRepo: taskRepo,
|
||||
taskListRepo: taskListRepo,
|
||||
taskStatusRepo: taskStatusRepo,
|
||||
noteRepo: noteRepo,
|
||||
categoryRepo: categoryRepo,
|
||||
@@ -121,13 +124,10 @@ func toObjectIDs(hexIDs []string) ([]bson.ObjectID, error) {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (s *TaskService) validateCategory(ctx context.Context, spaceID bson.ObjectID, categoryID *bson.ObjectID) error {
|
||||
if categoryID == nil {
|
||||
return nil
|
||||
}
|
||||
category, err := s.categoryRepo.GetCategoryByID(ctx, *categoryID)
|
||||
if err != nil || category.SpaceID != spaceID {
|
||||
return errors.New("invalid category")
|
||||
func (s *TaskService) validateTaskList(ctx context.Context, spaceID, taskListID bson.ObjectID) error {
|
||||
taskList, err := s.taskListRepo.GetTaskListByID(ctx, taskListID)
|
||||
if err != nil || taskList.SpaceID != spaceID {
|
||||
return errors.New("invalid task list")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -209,14 +209,6 @@ func (s *TaskService) CreateTask(ctx context.Context, spaceID, userID bson.Objec
|
||||
return nil, err
|
||||
}
|
||||
|
||||
categoryID, err := toObjectIDPtr(req.CategoryID)
|
||||
if err != nil {
|
||||
return nil, errors.New("invalid category")
|
||||
}
|
||||
if err := s.validateCategory(ctx, spaceID, categoryID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
parentTaskID, err := toObjectIDPtr(req.ParentTaskID)
|
||||
if err != nil {
|
||||
return nil, errors.New("invalid parent task")
|
||||
@@ -226,6 +218,14 @@ func (s *TaskService) CreateTask(ctx context.Context, spaceID, userID bson.Objec
|
||||
return nil, err
|
||||
}
|
||||
|
||||
taskListID, err := bson.ObjectIDFromHex(strings.TrimSpace(req.TaskListID))
|
||||
if err != nil {
|
||||
return nil, errors.New("invalid task list")
|
||||
}
|
||||
if err := s.validateTaskList(ctx, spaceID, taskListID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
noteLinks, err := toObjectIDs(req.NoteLinks)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -258,11 +258,19 @@ func (s *TaskService) CreateTask(ctx context.Context, spaceID, userID bson.Objec
|
||||
statusID = parsedStatusID
|
||||
}
|
||||
|
||||
if parentTaskID != nil {
|
||||
parent, parentErr := s.taskRepo.GetTaskByID(ctx, *parentTaskID)
|
||||
if parentErr != nil || parent.SpaceID != spaceID {
|
||||
return nil, errors.New("invalid parent task")
|
||||
}
|
||||
taskListID = parent.TaskListID
|
||||
}
|
||||
|
||||
task := &entities.Task{
|
||||
SpaceID: spaceID,
|
||||
Title: strings.TrimSpace(req.Title),
|
||||
Description: strings.TrimSpace(req.Description),
|
||||
CategoryID: categoryID,
|
||||
TaskListID: taskListID,
|
||||
StatusID: statusID,
|
||||
ParentTaskID: parentTaskID,
|
||||
Depth: depth,
|
||||
@@ -318,7 +326,7 @@ func (s *TaskService) GetTaskByID(ctx context.Context, spaceID, taskID, userID b
|
||||
func (s *TaskService) ListTasks(
|
||||
ctx context.Context,
|
||||
spaceID, userID bson.ObjectID,
|
||||
categoryID, statusID, parentTaskID *string,
|
||||
taskListID, statusID, parentTaskID *string,
|
||||
) ([]*dto.TaskDTO, error) {
|
||||
if err := s.requireSpaceAccess(ctx, userID, spaceID); err != nil {
|
||||
return nil, err
|
||||
@@ -328,12 +336,12 @@ func (s *TaskService) ListTasks(
|
||||
}
|
||||
|
||||
filters := map[string]any{}
|
||||
if categoryID != nil && strings.TrimSpace(*categoryID) != "" {
|
||||
id, err := bson.ObjectIDFromHex(*categoryID)
|
||||
if taskListID != nil && strings.TrimSpace(*taskListID) != "" {
|
||||
id, err := bson.ObjectIDFromHex(*taskListID)
|
||||
if err != nil {
|
||||
return nil, errors.New("invalid category filter")
|
||||
return nil, errors.New("invalid task list filter")
|
||||
}
|
||||
filters["category_id"] = id
|
||||
filters["task_list_id"] = id
|
||||
}
|
||||
if statusID != nil && strings.TrimSpace(*statusID) != "" {
|
||||
id, err := bson.ObjectIDFromHex(*statusID)
|
||||
@@ -453,19 +461,21 @@ func (s *TaskService) UpdateTask(ctx context.Context, spaceID, taskID, userID bs
|
||||
task.Description = strings.TrimSpace(*req.Description)
|
||||
}
|
||||
|
||||
if req.CategoryID != nil {
|
||||
if strings.TrimSpace(*req.CategoryID) == "" {
|
||||
task.CategoryID = nil
|
||||
} else {
|
||||
categoryID, parseErr := bson.ObjectIDFromHex(*req.CategoryID)
|
||||
if parseErr != nil {
|
||||
return nil, errors.New("invalid category")
|
||||
}
|
||||
task.CategoryID = &categoryID
|
||||
if req.TaskListID != nil {
|
||||
if strings.TrimSpace(*req.TaskListID) == "" {
|
||||
return nil, errors.New("task list is required")
|
||||
}
|
||||
if err := s.validateCategory(ctx, spaceID, task.CategoryID); err != nil {
|
||||
taskListID, parseErr := bson.ObjectIDFromHex(*req.TaskListID)
|
||||
if parseErr != nil {
|
||||
return nil, errors.New("invalid task list")
|
||||
}
|
||||
if task.ParentTaskID != nil {
|
||||
return nil, errors.New("subtasks inherit task list from parent")
|
||||
}
|
||||
if err := s.validateTaskList(ctx, spaceID, taskListID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
task.TaskListID = taskListID
|
||||
}
|
||||
|
||||
if req.ParentTaskID != nil {
|
||||
@@ -486,6 +496,11 @@ func (s *TaskService) UpdateTask(ctx context.Context, spaceID, taskID, userID bs
|
||||
}
|
||||
task.ParentTaskID = &parentID
|
||||
task.Depth = depth
|
||||
parentTask, parentErr := s.taskRepo.GetTaskByID(ctx, parentID)
|
||||
if parentErr != nil || parentTask.SpaceID != spaceID {
|
||||
return nil, errors.New("invalid parent task")
|
||||
}
|
||||
task.TaskListID = parentTask.TaskListID
|
||||
}
|
||||
}
|
||||
|
||||
@@ -670,6 +685,144 @@ func (s *TaskService) UnlinkNoteFromTask(ctx context.Context, spaceID, taskID, n
|
||||
return dto.NewTaskDTO(task), nil
|
||||
}
|
||||
|
||||
func (s *TaskService) ListTaskLists(ctx context.Context, spaceID, userID bson.ObjectID) ([]*dto.TaskListDTO, error) {
|
||||
if err := s.requireSpaceAccess(ctx, userID, spaceID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
lists, err := s.taskListRepo.ListTaskLists(ctx, spaceID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := make([]*dto.TaskListDTO, 0, len(lists))
|
||||
for _, list := range lists {
|
||||
result = append(result, dto.NewTaskListDTO(list))
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (s *TaskService) CreateTaskList(ctx context.Context, spaceID, userID bson.ObjectID, req *dto.CreateTaskListRequest) (*dto.TaskListDTO, error) {
|
||||
if err := s.requireSpaceAccess(ctx, userID, spaceID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hasPermission, err := s.hasTaskPermission(ctx, userID, spaceID, "tasks.create")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !hasPermission {
|
||||
return nil, errors.New("insufficient permissions")
|
||||
}
|
||||
|
||||
name := strings.TrimSpace(req.Name)
|
||||
if name == "" {
|
||||
return nil, errors.New("task list name is required")
|
||||
}
|
||||
|
||||
var categoryID *bson.ObjectID
|
||||
if req.CategoryID != nil && strings.TrimSpace(*req.CategoryID) != "" {
|
||||
id, parseErr := bson.ObjectIDFromHex(*req.CategoryID)
|
||||
if parseErr != nil {
|
||||
return nil, errors.New("invalid category")
|
||||
}
|
||||
category, categoryErr := s.categoryRepo.GetCategoryByID(ctx, id)
|
||||
if categoryErr != nil || category.SpaceID != spaceID {
|
||||
return nil, errors.New("invalid category")
|
||||
}
|
||||
categoryID = &id
|
||||
}
|
||||
|
||||
list := &entities.TaskList{
|
||||
SpaceID: spaceID,
|
||||
CategoryID: categoryID,
|
||||
Name: name,
|
||||
Description: strings.TrimSpace(req.Description),
|
||||
CreatedBy: userID,
|
||||
UpdatedBy: userID,
|
||||
}
|
||||
|
||||
if err := s.taskListRepo.CreateTaskList(ctx, list); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dto.NewTaskListDTO(list), nil
|
||||
}
|
||||
|
||||
func (s *TaskService) UpdateTaskList(ctx context.Context, spaceID, taskListID, userID bson.ObjectID, req *dto.UpdateTaskListRequest) (*dto.TaskListDTO, error) {
|
||||
if err := s.requireSpaceAccess(ctx, userID, spaceID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hasPermission, err := s.hasTaskPermission(ctx, userID, spaceID, "tasks.edit")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !hasPermission {
|
||||
return nil, errors.New("insufficient permissions")
|
||||
}
|
||||
|
||||
list, err := s.taskListRepo.GetTaskListByID(ctx, taskListID)
|
||||
if err != nil || list.SpaceID != spaceID {
|
||||
return nil, errors.New("task list not found")
|
||||
}
|
||||
|
||||
if req.Name != nil {
|
||||
name := strings.TrimSpace(*req.Name)
|
||||
if name == "" {
|
||||
return nil, errors.New("task list name is required")
|
||||
}
|
||||
list.Name = name
|
||||
}
|
||||
if req.Description != nil {
|
||||
list.Description = strings.TrimSpace(*req.Description)
|
||||
}
|
||||
if req.CategoryID != nil {
|
||||
if strings.TrimSpace(*req.CategoryID) == "" {
|
||||
list.CategoryID = nil
|
||||
} else {
|
||||
categoryID, parseErr := bson.ObjectIDFromHex(*req.CategoryID)
|
||||
if parseErr != nil {
|
||||
return nil, errors.New("invalid category")
|
||||
}
|
||||
category, categoryErr := s.categoryRepo.GetCategoryByID(ctx, categoryID)
|
||||
if categoryErr != nil || category.SpaceID != spaceID {
|
||||
return nil, errors.New("invalid category")
|
||||
}
|
||||
list.CategoryID = &categoryID
|
||||
}
|
||||
}
|
||||
|
||||
list.UpdatedBy = userID
|
||||
if err := s.taskListRepo.UpdateTaskList(ctx, list); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dto.NewTaskListDTO(list), nil
|
||||
}
|
||||
|
||||
func (s *TaskService) DeleteTaskList(ctx context.Context, spaceID, taskListID, userID bson.ObjectID) error {
|
||||
if err := s.requireSpaceAccess(ctx, userID, spaceID); err != nil {
|
||||
return err
|
||||
}
|
||||
hasPermission, err := s.hasTaskPermission(ctx, userID, spaceID, "tasks.delete")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !hasPermission {
|
||||
return errors.New("insufficient permissions")
|
||||
}
|
||||
|
||||
list, err := s.taskListRepo.GetTaskListByID(ctx, taskListID)
|
||||
if err != nil || list.SpaceID != spaceID {
|
||||
return errors.New("task list not found")
|
||||
}
|
||||
|
||||
tasks, err := s.taskRepo.ListTasks(ctx, spaceID, map[string]any{"task_list_id": taskListID})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(tasks) > 0 {
|
||||
return errors.New("cannot delete task list with tasks")
|
||||
}
|
||||
|
||||
return s.taskListRepo.DeleteTaskList(ctx, taskListID)
|
||||
}
|
||||
|
||||
func (s *TaskService) ListStatuses(ctx context.Context, spaceID, userID bson.ObjectID) ([]*dto.TaskStatusDTO, error) {
|
||||
if err := s.requireSpaceAccess(ctx, userID, spaceID); err != nil {
|
||||
return nil, err
|
||||
|
||||
Reference in New Issue
Block a user