package services import ( "context" "github.com/AchievementNetwork/stringset" "github.com/gissleh/stufflog/database" "github.com/gissleh/stufflog/internal/auth" "github.com/gissleh/stufflog/internal/slerrors" "github.com/gissleh/stufflog/models" "golang.org/x/sync/errgroup" ) // Loader loads the stuff. type Loader struct { DB database.Database } func (l *Loader) FindGroup(ctx context.Context, id string) (*models.GroupResult, error) { group, err := l.DB.Groups().Find(ctx, id) if err != nil { return nil, err } if group.UserID != auth.UserID(ctx) { return nil, slerrors.NotFound("Goal") } result := &models.GroupResult{Group: *group} result.Items, err = l.DB.Items().List(ctx, models.ItemFilter{ UserID: auth.UserID(ctx), GroupIDs: []string{group.ID}, }) if err != nil { return nil, err } return result, nil } func (l *Loader) ListGroups(ctx context.Context, filter models.GroupFilter) ([]*models.GroupResult, error) { filter.UserID = auth.UserID(ctx) groups, err := l.DB.Groups().List(ctx, filter) if err != nil { return nil, err } groupIDs := make([]string, 0, len(groups)) for _, group := range groups { groupIDs = append(groupIDs, group.ID) } items, err := l.DB.Items().List(ctx, models.ItemFilter{ UserID: auth.UserID(ctx), GroupIDs: groupIDs, }) results := make([]*models.GroupResult, len(groups)) for i, group := range groups { results[i] = &models.GroupResult{Group: *group, Items: []*models.Item{}} for _, item := range items { if item.GroupID == group.ID { results[i].Items = append(results[i].Items, item) } } } return results, nil } func (l *Loader) FindItem(ctx context.Context, id string) (*models.ItemResult, error) { item, err := l.DB.Items().Find(ctx, id) if err != nil { return nil, err } if item.UserID != auth.UserID(ctx) { return nil, slerrors.NotFound("Item") } result := &models.ItemResult{Item: *item} result.Group, err = l.DB.Groups().Find(ctx, item.GroupID) if err != nil { return nil, err } return result, nil } func (l *Loader) ListItems(ctx context.Context, filter models.ItemFilter) ([]*models.ItemResult, error) { filter.UserID = auth.UserID(ctx) items, err := l.DB.Items().List(ctx, filter) if err != nil { return nil, err } groupIDs := make([]string, 0, len(items)) for _, item := range items { groupIDs = append(groupIDs, item.GroupID) } groups, err := l.DB.Groups().List(ctx, models.GroupFilter{ UserID: auth.UserID(ctx), IDs: groupIDs, }) results := make([]*models.ItemResult, len(items)) for i, item := range items { results[i] = &models.ItemResult{Item: *item} for _, group := range groups { if item.GroupID == group.ID { results[i].Group = group break } } } return results, nil } func (l *Loader) FindLog(ctx context.Context, id string) (*models.LogResult, error) { log, err := l.DB.Logs().Find(ctx, id) if err != nil { return nil, err } if log.UserID != auth.UserID(ctx) { return nil, slerrors.NotFound("Goal") } result := &models.LogResult{ Log: *log, Task: nil, } result.Task, _ = l.DB.Tasks().Find(ctx, id) result.Item, _ = l.DB.Items().Find(ctx, log.ItemID) return result, nil } func (l *Loader) ListLogs(ctx context.Context, filter models.LogFilter) ([]*models.LogResult, error) { filter.UserID = auth.UserID(ctx) logs, err := l.DB.Logs().List(ctx, filter) if err != nil { return nil, err } taskIDs := stringset.New() itemIDs := stringset.New() for _, log := range logs { taskIDs.Add(log.TaskID) itemIDs.Add(log.ItemID) } tasks, err := l.DB.Tasks().List(ctx, models.TaskFilter{ UserID: auth.UserID(ctx), IDs: taskIDs.Strings(), }) if err != nil { return nil, err } items, err := l.DB.Items().List(ctx, models.ItemFilter{ UserID: auth.UserID(ctx), IDs: itemIDs.Strings(), }) if err != nil { return nil, err } results := make([]*models.LogResult, len(logs)) for i, log := range logs { results[i] = &models.LogResult{ Log: *log, Task: nil, } for _, task := range tasks { if task.ID == log.TaskID { results[i].Task = task break } } for _, item := range items { if item.ID == log.ItemID { results[i].Item = item break } } } return results, nil } func (l *Loader) FindProject(ctx context.Context, id string) (*models.ProjectResult, error) { project, err := l.DB.Projects().Find(ctx, id) if err != nil { return nil, err } if project.UserID != auth.UserID(ctx) { return nil, slerrors.NotFound("Goal") } result := &models.ProjectResult{Project: *project} tasks, err := l.DB.Tasks().List(ctx, models.TaskFilter{ UserID: auth.UserID(ctx), ProjectIDs: []string{project.ID}, }) taskIDs := make([]string, 0, len(tasks)) itemIDs := stringset.New() for _, task := range tasks { taskIDs = append(taskIDs, task.ID) itemIDs.Add(task.ItemID) } logs, err := l.DB.Logs().List(ctx, models.LogFilter{ UserID: auth.UserID(ctx), TaskIDs: taskIDs, }) if err != nil { return nil, err } items, err := l.DB.Items().List(ctx, models.ItemFilter{ UserID: auth.UserID(ctx), IDs: itemIDs.Strings(), }) if err != nil { return nil, err } result.Tasks = make([]*models.TaskResult, len(tasks)) for i, task := range tasks { result.Tasks[i] = &models.TaskResult{ Logs: []*models.Log{}, } result.Tasks[i].Task = *task for _, log := range logs { if log.TaskID == task.ID { result.Tasks[i].Logs = append(result.Tasks[i].Logs, log) } } for _, item := range items { if item.ID == task.ItemID { result.Tasks[i].Item = item break } } result.Tasks[i].CompletedAmount = len(result.Tasks[i].Logs) } return result, nil } func (l *Loader) ListProjects(ctx context.Context, filter models.ProjectFilter) ([]*models.ProjectResult, error) { filter.UserID = auth.UserID(ctx) projects, err := l.DB.Projects().List(ctx, filter) if err != nil { return nil, err } projectIDs := make([]string, 0, len(projects)) for _, project := range projects { projectIDs = append(projectIDs, project.ID) } tasks, err := l.DB.Tasks().List(ctx, models.TaskFilter{ UserID: auth.UserID(ctx), ProjectIDs: projectIDs, }) if err != nil { return nil, err } taskIDs := make([]string, 0, len(tasks)) itemIDs := stringset.New() for _, task := range tasks { taskIDs = append(taskIDs, task.ID) itemIDs.Add(task.ItemID) } logs, err := l.DB.Logs().List(ctx, models.LogFilter{ UserID: auth.UserID(ctx), TaskIDs: taskIDs, }) if err != nil { return nil, err } items, err := l.DB.Items().List(ctx, models.ItemFilter{ UserID: auth.UserID(ctx), IDs: itemIDs.Strings(), }) if err != nil { return nil, err } results := make([]*models.ProjectResult, len(projects)) for i, project := range projects { results[i] = &models.ProjectResult{Project: *project} results[i].Tasks = make([]*models.TaskResult, 0, 16) for _, task := range tasks { if task.ProjectID != project.ID { continue } taskResult := &models.TaskResult{ Task: *task, Logs: []*models.Log{}, } for _, log := range logs { if log.TaskID == task.ID { taskResult.Logs = append(taskResult.Logs, log) } } for _, item := range items { if item.ID == task.ItemID { taskResult.Item = item break } } taskResult.CompletedAmount = len(taskResult.Logs) results[i].Tasks = append(results[i].Tasks, taskResult) } } return results, nil } func (l *Loader) FindTask(ctx context.Context, id string) (*models.TaskResult, error) { task, err := l.DB.Tasks().Find(ctx, id) if err != nil { return nil, err } if task.UserID != auth.UserID(ctx) { return nil, slerrors.NotFound("Goal") } result := &models.TaskResult{Task: *task} result.Item, _ = l.DB.Items().Find(ctx, task.ItemID) result.Project, _ = l.DB.Projects().Find(ctx, task.ProjectID) result.Logs, err = l.DB.Logs().List(ctx, models.LogFilter{ UserID: task.UserID, TaskIDs: []string{task.ID}, }) if err != nil { return nil, err } result.CompletedAmount = len(result.Logs) return result, nil } func (l *Loader) ListTasks(ctx context.Context, filter models.TaskFilter) ([]*models.TaskResult, error) { filter.UserID = auth.UserID(ctx) tasks, err := l.DB.Tasks().List(ctx, filter) if err != nil { return nil, err } if len(tasks) == 0 { return []*models.TaskResult{}, nil } taskIDs := make([]string, 0, len(tasks)) itemIDs := stringset.New() projectIDs := stringset.New() for _, task := range tasks { taskIDs = append(taskIDs, task.ID) itemIDs.Add(task.ItemID) projectIDs.Add(task.ProjectID) } logs, err := l.DB.Logs().List(ctx, models.LogFilter{ UserID: auth.UserID(ctx), TaskIDs: taskIDs, }) if err != nil { return nil, err } items, err := l.DB.Items().List(ctx, models.ItemFilter{ UserID: auth.UserID(ctx), IDs: itemIDs.Strings(), }) if err != nil { return nil, err } projects, err := l.DB.Projects().List(ctx, models.ProjectFilter{ UserID: auth.UserID(ctx), IDs: projectIDs.Strings(), }) if err != nil { return nil, err } results := make([]*models.TaskResult, 0, len(tasks)) for _, task := range tasks { result := &models.TaskResult{ Task: *task, Logs: []*models.Log{}, } for _, log := range logs { if log.TaskID == task.ID { result.Logs = append(result.Logs, log) } } for _, item := range items { if item.ID == task.ItemID { result.Item = item break } } for _, project := range projects { if project.ID == task.ProjectID { result.Project = project break } } result.CompletedAmount = len(result.Logs) results = append(results, result) } return results, nil } func (l *Loader) FindGoal(ctx context.Context, id string) (*models.GoalResult, error) { goal, err := l.DB.Goals().Find(ctx, id) if err != nil { return nil, err } if goal.UserID != auth.UserID(ctx) { return nil, slerrors.NotFound("Goal") } return l.populateGoals(ctx, goal) } func (l *Loader) ListGoals(ctx context.Context, filter models.GoalFilter) ([]*models.GoalResult, error) { filter.UserID = auth.UserID(ctx) goals, err := l.DB.Goals().List(ctx, filter) if err != nil { return nil, err } results := make([]*models.GoalResult, len(goals)) eg := errgroup.Group{} for i := range results { index := i // Required to avoid race condition. eg.Go(func() error { res, err := l.populateGoals(ctx, goals[index]) if err != nil { return err } results[index] = res return nil }) } err = eg.Wait() if err != nil { return nil, err } return results, nil } func (l *Loader) populateGoals(ctx context.Context, goal *models.Goal) (*models.GoalResult, error) { userID := auth.UserID(ctx) result := &models.GoalResult{ Goal: *goal, Group: nil, Items: nil, Logs: nil, CompletedAmount: 0, } result.Group, _ = l.DB.Groups().Find(ctx, goal.GroupID) if result.Group != nil { // Get items items, err := l.DB.Items().List(ctx, models.ItemFilter{ UserID: userID, GroupIDs: []string{goal.GroupID}, }) if err != nil { return nil, err } itemIDs := make([]string, 0, len(items)) for _, item := range items { result.Items = append(result.Items, &models.GoalResultItem{ Item: *item, CompletedAmount: 0, }) itemIDs = append(itemIDs, item.ID) } // Get logs logs, err := l.DB.Logs().List(ctx, models.LogFilter{ UserID: userID, ItemIDs: itemIDs, MinTime: &goal.StartTime, MaxTime: &goal.EndTime, }) // Get tasks taskIDs := make([]string, 0, len(result.Logs)) for _, log := range logs { taskIDs = append(taskIDs, log.TaskID) } tasks, err := l.DB.Tasks().List(ctx, models.TaskFilter{ UserID: userID, IDs: taskIDs, }) // Apply logs result.Logs = make([]*models.LogResult, 0, len(logs)) for _, log := range logs { resultLog := &models.LogResult{ Log: *log, } for _, task := range tasks { if task.ID == log.TaskID { resultLog.Task = task for _, item := range result.Items { if task.ItemID == item.ID { item.CompletedAmount += 1 result.CompletedAmount += item.GroupWeight break } resultLog.Item = &item.Item } break } } result.Logs = append(result.Logs, resultLog) } } return result, nil }