You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
659 lines
15 KiB
659 lines
15 KiB
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,
|
|
}
|
|
|
|
task, err := l.DB.Tasks().Find(ctx, log.TaskID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
project, err := l.DB.Projects().Find(ctx, task.ProjectID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
result.Task = &models.TaskWithProject{
|
|
Task: *task,
|
|
Project: project,
|
|
}
|
|
result.Item, _ = l.DB.Items().Find(ctx, log.ItemID)
|
|
if log.SecondaryItemID != nil {
|
|
result.SecondaryItem, _ = l.DB.Items().Find(ctx, *log.SecondaryItemID)
|
|
}
|
|
|
|
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()
|
|
projectIDs := stringset.New()
|
|
for _, log := range logs {
|
|
taskIDs.Add(log.TaskID)
|
|
itemIDs.Add(log.ItemID)
|
|
if log.SecondaryItemID != nil {
|
|
itemIDs.Add(*log.SecondaryItemID)
|
|
}
|
|
}
|
|
tasks, err := l.DB.Tasks().List(ctx, models.TaskFilter{
|
|
UserID: auth.UserID(ctx),
|
|
IDs: taskIDs.Strings(),
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, task := range tasks {
|
|
projectIDs.Add(task.ProjectID)
|
|
}
|
|
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.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 = &models.TaskWithProject{Task: *task}
|
|
break
|
|
}
|
|
}
|
|
|
|
if results[i].Task != nil {
|
|
for _, project := range projects {
|
|
if project.ID == results[i].Task.ProjectID {
|
|
results[i].Task.Project = project
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
for _, item := range items {
|
|
if item.ID == log.ItemID {
|
|
results[i].Item = item
|
|
}
|
|
if log.SecondaryItemID != nil && item.ID == *log.SecondaryItemID {
|
|
results[i].SecondaryItem = item
|
|
}
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|
|
}
|
|
for _, log := range result.Tasks[i].Logs {
|
|
result.Tasks[i].CompletedAmount += log.Amount(result.Tasks[i].ItemID)
|
|
}
|
|
}
|
|
|
|
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, links, err := l.DB.Tasks().ListWithLinks(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 {
|
|
foundLink := false
|
|
|
|
for _, link := range links {
|
|
if link.TaskID == task.ID && link.ProjectID == project.ID {
|
|
foundLink = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if !foundLink {
|
|
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
|
|
}
|
|
}
|
|
for _, log := range taskResult.Logs {
|
|
taskResult.CompletedAmount += log.Amount(taskResult.ItemID)
|
|
}
|
|
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
|
|
}
|
|
|
|
for _, log := range result.Logs {
|
|
result.CompletedAmount += log.Amount(result.ItemID)
|
|
}
|
|
|
|
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
|
|
}
|
|
}
|
|
|
|
for _, log := range result.Logs {
|
|
result.CompletedAmount += log.Amount(result.ItemID)
|
|
}
|
|
|
|
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,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Get tasks
|
|
taskIDs := stringset.New()
|
|
for _, log := range logs {
|
|
taskIDs.Add(log.TaskID)
|
|
}
|
|
tasks, err := l.DB.Tasks().List(ctx, models.TaskFilter{
|
|
UserID: userID,
|
|
IDs: taskIDs.Strings(),
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
projectIDs := stringset.New()
|
|
for _, task := range tasks {
|
|
projectIDs.Add(task.ProjectID)
|
|
}
|
|
projects, err := l.DB.Projects().List(ctx, models.ProjectFilter{
|
|
UserID: userID,
|
|
IDs: projectIDs.Strings(),
|
|
})
|
|
|
|
// Apply logs
|
|
result.Logs = make([]*models.GoalResultLog, 0, len(logs))
|
|
for _, log := range logs {
|
|
resultLog := &models.GoalResultLog{
|
|
LogResult: models.LogResult{Log: *log},
|
|
}
|
|
contributes := false
|
|
|
|
for _, task := range tasks {
|
|
if task.ID == log.TaskID {
|
|
resultLog.Task = &models.TaskWithProject{Task: *task}
|
|
|
|
for _, project := range projects {
|
|
if project.ID == task.ProjectID {
|
|
resultLog.Task.Project = project
|
|
break
|
|
}
|
|
}
|
|
|
|
break
|
|
}
|
|
}
|
|
|
|
for _, item := range result.Items {
|
|
amount := log.Amount(item.ID)
|
|
if amount > 0 && goal.Accepts(&item.Item, &resultLog.Task.Task) {
|
|
item.CompletedAmount += amount
|
|
|
|
if goal.Unweighted {
|
|
if item.GroupWeight > 0 {
|
|
result.CompletedAmount += amount
|
|
}
|
|
} else {
|
|
result.CompletedAmount += amount * item.GroupWeight
|
|
}
|
|
|
|
contributes = true
|
|
} else {
|
|
amount = 0
|
|
}
|
|
|
|
if item.ID == log.ItemID {
|
|
resultLog.Item = &item.Item
|
|
|
|
if amount > 0 {
|
|
resultLog.ItemCounted = true
|
|
}
|
|
|
|
if log.SecondaryItemID == nil {
|
|
break
|
|
}
|
|
}
|
|
|
|
if log.SecondaryItemID != nil && item.ID == *log.SecondaryItemID {
|
|
if amount > 0 {
|
|
resultLog.SecondaryItemCounted = true
|
|
}
|
|
|
|
resultLog.SecondaryItem = &item.Item
|
|
}
|
|
}
|
|
|
|
if contributes {
|
|
result.Logs = append(result.Logs, resultLog)
|
|
}
|
|
}
|
|
}
|
|
|
|
return result, nil
|
|
}
|