package database import ( "context" "errors" "time" "gitea.hostxtra.co.uk/mrhid6/notely/backend/internal/domain/entities" "go.mongodb.org/mongo-driver/v2/bson" "go.mongodb.org/mongo-driver/v2/mongo" "go.mongodb.org/mongo-driver/v2/mongo/options" ) // TaskRepository implements task data access. type TaskRepository struct { collection *mongo.Collection } // NewTaskRepository creates a new task repository. func NewTaskRepository(db *mongo.Database) *TaskRepository { return &TaskRepository{collection: db.Collection("tasks")} } func (r *TaskRepository) CreateTask(ctx context.Context, task *entities.Task) error { task.ID = bson.NewObjectID() task.CreatedAt = time.Now() task.UpdatedAt = time.Now() if task.NoteLinks == nil { task.NoteLinks = []bson.ObjectID{} } _, err := r.collection.InsertOne(ctx, task) return err } func (r *TaskRepository) GetTaskByID(ctx context.Context, id bson.ObjectID) (*entities.Task, error) { var task entities.Task err := r.collection.FindOne(ctx, bson.M{"_id": id}).Decode(&task) if err != nil { if err == mongo.ErrNoDocuments { return nil, errors.New("task not found") } return nil, err } return &task, nil } func (r *TaskRepository) ListTasks(ctx context.Context, spaceID bson.ObjectID, filters map[string]any) ([]*entities.Task, error) { query := bson.M{"space_id": spaceID} for k, v := range filters { query[k] = v } opts := options.Find().SetSort(bson.D{{Key: "updated_at", Value: -1}}) cursor, err := r.collection.Find(ctx, query, opts) if err != nil { return nil, err } defer cursor.Close(ctx) var tasks []*entities.Task if err := cursor.All(ctx, &tasks); err != nil { return nil, err } return tasks, nil } func (r *TaskRepository) SearchTasks(ctx context.Context, spaceID bson.ObjectID, query string) ([]*entities.Task, error) { cursor, err := r.collection.Find(ctx, bson.M{ "space_id": spaceID, "$or": []bson.M{ {"title": bson.M{"$regex": query, "$options": "i"}}, {"description": bson.M{"$regex": query, "$options": "i"}}, }, }, options.Find().SetSort(bson.D{{Key: "updated_at", Value: -1}}).SetLimit(30)) if err != nil { return nil, err } defer cursor.Close(ctx) var tasks []*entities.Task if err := cursor.All(ctx, &tasks); err != nil { return nil, err } return tasks, nil } func (r *TaskRepository) UpdateTask(ctx context.Context, task *entities.Task) error { task.UpdatedAt = time.Now() _, err := r.collection.ReplaceOne(ctx, bson.M{"_id": task.ID}, task) return err } func (r *TaskRepository) DeleteTask(ctx context.Context, id bson.ObjectID) error { _, err := r.collection.DeleteOne(ctx, bson.M{"_id": id}) return err } func (r *TaskRepository) DeleteTasksBySpaceID(ctx context.Context, spaceID bson.ObjectID) error { _, err := r.collection.DeleteMany(ctx, bson.M{"space_id": spaceID}) return err } func (r *TaskRepository) CountChildren(ctx context.Context, parentTaskID bson.ObjectID) (int64, error) { return r.collection.CountDocuments(ctx, bson.M{"parent_task_id": parentTaskID}) } func (r *TaskRepository) EnsureIndexes(ctx context.Context) error { _, err := r.collection.Indexes().CreateMany(ctx, []mongo.IndexModel{ {Keys: bson.D{{Key: "space_id", Value: 1}, {Key: "updated_at", Value: -1}}}, {Keys: bson.D{{Key: "space_id", Value: 1}, {Key: "status_id", Value: 1}}}, {Keys: bson.D{{Key: "space_id", Value: 1}, {Key: "task_list_id", Value: 1}}}, {Keys: bson.D{{Key: "space_id", Value: 1}, {Key: "parent_task_id", Value: 1}}}, {Keys: bson.D{{Key: "space_id", Value: 1}, {Key: "note_links", Value: 1}}}, }) return err } // TaskListRepository implements task list data access. type TaskListRepository struct { collection *mongo.Collection } // NewTaskListRepository creates a new task list repository. func NewTaskListRepository(db *mongo.Database) *TaskListRepository { return &TaskListRepository{collection: db.Collection("task_lists")} } func (r *TaskListRepository) CreateTaskList(ctx context.Context, list *entities.TaskList) error { list.ID = bson.NewObjectID() list.CreatedAt = time.Now() list.UpdatedAt = time.Now() _, err := r.collection.InsertOne(ctx, list) return err } func (r *TaskListRepository) GetTaskListByID(ctx context.Context, id bson.ObjectID) (*entities.TaskList, error) { var list entities.TaskList err := r.collection.FindOne(ctx, bson.M{"_id": id}).Decode(&list) if err != nil { if err == mongo.ErrNoDocuments { return nil, errors.New("task list not found") } return nil, err } return &list, nil } func (r *TaskListRepository) ListTaskLists(ctx context.Context, spaceID bson.ObjectID) ([]*entities.TaskList, error) { cursor, err := r.collection.Find(ctx, bson.M{"space_id": spaceID}, options.Find().SetSort(bson.D{{Key: "name", Value: 1}})) if err != nil { return nil, err } defer cursor.Close(ctx) var lists []*entities.TaskList if err := cursor.All(ctx, &lists); err != nil { return nil, err } return lists, nil } func (r *TaskListRepository) ListTaskListsByCategory(ctx context.Context, spaceID bson.ObjectID, categoryID bson.ObjectID) ([]*entities.TaskList, error) { cursor, err := r.collection.Find(ctx, bson.M{"space_id": spaceID, "category_id": categoryID}, options.Find().SetSort(bson.D{{Key: "name", Value: 1}})) if err != nil { return nil, err } defer cursor.Close(ctx) var lists []*entities.TaskList if err := cursor.All(ctx, &lists); err != nil { return nil, err } return lists, nil } func (r *TaskListRepository) UpdateTaskList(ctx context.Context, list *entities.TaskList) error { list.UpdatedAt = time.Now() _, err := r.collection.ReplaceOne(ctx, bson.M{"_id": list.ID}, list) return err } func (r *TaskListRepository) DeleteTaskList(ctx context.Context, id bson.ObjectID) error { _, err := r.collection.DeleteOne(ctx, bson.M{"_id": id}) return err } func (r *TaskListRepository) DeleteTaskListsBySpaceID(ctx context.Context, spaceID bson.ObjectID) error { _, err := r.collection.DeleteMany(ctx, bson.M{"space_id": spaceID}) return err } func (r *TaskListRepository) EnsureIndexes(ctx context.Context) error { _, err := r.collection.Indexes().CreateMany(ctx, []mongo.IndexModel{ {Keys: bson.D{{Key: "space_id", Value: 1}, {Key: "name", Value: 1}}, Options: options.Index().SetUnique(true)}, {Keys: bson.D{{Key: "space_id", Value: 1}, {Key: "category_id", Value: 1}}}, }) return err } // TaskStatusRepository implements task status data access. type TaskStatusRepository struct { collection *mongo.Collection } // NewTaskStatusRepository creates a new task status repository. func NewTaskStatusRepository(db *mongo.Database) *TaskStatusRepository { return &TaskStatusRepository{collection: db.Collection("task_statuses")} } func (r *TaskStatusRepository) CreateStatus(ctx context.Context, status *entities.TaskStatus) error { status.ID = bson.NewObjectID() status.CreatedAt = time.Now() status.UpdatedAt = time.Now() _, err := r.collection.InsertOne(ctx, status) return err } func (r *TaskStatusRepository) GetStatusByID(ctx context.Context, id bson.ObjectID) (*entities.TaskStatus, error) { var status entities.TaskStatus err := r.collection.FindOne(ctx, bson.M{"_id": id}).Decode(&status) if err != nil { if err == mongo.ErrNoDocuments { return nil, errors.New("task status not found") } return nil, err } return &status, nil } func (r *TaskStatusRepository) ListStatuses(ctx context.Context, spaceID bson.ObjectID) ([]*entities.TaskStatus, error) { cursor, err := r.collection.Find(ctx, bson.M{"space_id": spaceID}, options.Find().SetSort(bson.D{{Key: "order", Value: 1}})) if err != nil { return nil, err } defer cursor.Close(ctx) var statuses []*entities.TaskStatus if err := cursor.All(ctx, &statuses); err != nil { return nil, err } return statuses, nil } func (r *TaskStatusRepository) UpdateStatus(ctx context.Context, status *entities.TaskStatus) error { status.UpdatedAt = time.Now() _, err := r.collection.ReplaceOne(ctx, bson.M{"_id": status.ID}, status) return err } func (r *TaskStatusRepository) DeleteStatus(ctx context.Context, id bson.ObjectID) error { _, err := r.collection.DeleteOne(ctx, bson.M{"_id": id}) return err } func (r *TaskStatusRepository) EnsureIndexes(ctx context.Context) error { _, err := r.collection.Indexes().CreateMany(ctx, []mongo.IndexModel{ { Keys: bson.D{{Key: "space_id", Value: 1}, {Key: "name", Value: 1}}, Options: options.Index().SetUnique(true), }, { Keys: bson.D{{Key: "space_id", Value: 1}, {Key: "order", Value: 1}}, Options: options.Index().SetUnique(true), }, }) return err }