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.

742 lines
16 KiB

3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
  1. package services
  2. import (
  3. "context"
  4. "github.com/AchievementNetwork/stringset"
  5. "github.com/gissleh/stufflog/database"
  6. "github.com/gissleh/stufflog/internal/auth"
  7. "github.com/gissleh/stufflog/internal/slerrors"
  8. "github.com/gissleh/stufflog/models"
  9. "golang.org/x/sync/errgroup"
  10. )
  11. // Loader loads the stuff.
  12. type Loader struct {
  13. DB database.Database
  14. }
  15. func (l *Loader) FindGroup(ctx context.Context, id string) (*models.GroupResult, error) {
  16. group, err := l.DB.Groups().Find(ctx, id)
  17. if err != nil {
  18. return nil, err
  19. }
  20. if group.UserID != auth.UserID(ctx) {
  21. return nil, slerrors.NotFound("Goal")
  22. }
  23. result := &models.GroupResult{Group: *group}
  24. result.Items, err = l.DB.Items().List(ctx, models.ItemFilter{
  25. UserID: auth.UserID(ctx),
  26. GroupIDs: []string{group.ID},
  27. })
  28. if err != nil {
  29. return nil, err
  30. }
  31. return result, nil
  32. }
  33. func (l *Loader) ListGroups(ctx context.Context, filter models.GroupFilter) ([]*models.GroupResult, error) {
  34. filter.UserID = auth.UserID(ctx)
  35. groups, err := l.DB.Groups().List(ctx, filter)
  36. if err != nil {
  37. return nil, err
  38. }
  39. groupIDs := make([]string, 0, len(groups))
  40. for _, group := range groups {
  41. groupIDs = append(groupIDs, group.ID)
  42. }
  43. items, err := l.DB.Items().List(ctx, models.ItemFilter{
  44. UserID: auth.UserID(ctx),
  45. GroupIDs: groupIDs,
  46. })
  47. results := make([]*models.GroupResult, len(groups))
  48. for i, group := range groups {
  49. results[i] = &models.GroupResult{Group: *group, Items: []*models.Item{}}
  50. for _, item := range items {
  51. if item.GroupID == group.ID {
  52. results[i].Items = append(results[i].Items, item)
  53. }
  54. }
  55. }
  56. return results, nil
  57. }
  58. func (l *Loader) FindItem(ctx context.Context, id string) (*models.ItemResult, error) {
  59. item, err := l.DB.Items().Find(ctx, id)
  60. if err != nil {
  61. return nil, err
  62. }
  63. if item.UserID != auth.UserID(ctx) {
  64. return nil, slerrors.NotFound("Item")
  65. }
  66. result := &models.ItemResult{Item: *item}
  67. result.Group, err = l.DB.Groups().Find(ctx, item.GroupID)
  68. if err != nil {
  69. return nil, err
  70. }
  71. return result, nil
  72. }
  73. func (l *Loader) ListItems(ctx context.Context, filter models.ItemFilter) ([]*models.ItemResult, error) {
  74. filter.UserID = auth.UserID(ctx)
  75. items, err := l.DB.Items().List(ctx, filter)
  76. if err != nil {
  77. return nil, err
  78. }
  79. groupIDs := make([]string, 0, len(items))
  80. for _, item := range items {
  81. groupIDs = append(groupIDs, item.GroupID)
  82. }
  83. groups, err := l.DB.Groups().List(ctx, models.GroupFilter{
  84. UserID: auth.UserID(ctx),
  85. IDs: groupIDs,
  86. })
  87. results := make([]*models.ItemResult, len(items))
  88. for i, item := range items {
  89. results[i] = &models.ItemResult{Item: *item}
  90. for _, group := range groups {
  91. if item.GroupID == group.ID {
  92. results[i].Group = group
  93. break
  94. }
  95. }
  96. }
  97. return results, nil
  98. }
  99. func (l *Loader) FindLog(ctx context.Context, id string) (*models.LogResult, error) {
  100. log, err := l.DB.Logs().Find(ctx, id)
  101. if err != nil {
  102. return nil, err
  103. }
  104. if log.UserID != auth.UserID(ctx) {
  105. return nil, slerrors.NotFound("Goal")
  106. }
  107. result := &models.LogResult{
  108. Log: *log,
  109. Task: nil,
  110. }
  111. task, err := l.DB.Tasks().Find(ctx, log.TaskID)
  112. if err != nil {
  113. return nil, err
  114. }
  115. project, err := l.DB.Projects().Find(ctx, task.ProjectID)
  116. if err != nil {
  117. return nil, err
  118. }
  119. result.Task = &models.TaskWithProject{
  120. Task: *task,
  121. Project: project,
  122. }
  123. result.Item, _ = l.DB.Items().Find(ctx, log.ItemID)
  124. if log.SecondaryItemID != nil {
  125. result.SecondaryItem, _ = l.DB.Items().Find(ctx, *log.SecondaryItemID)
  126. }
  127. return result, nil
  128. }
  129. func (l *Loader) ListLogs(ctx context.Context, filter models.LogFilter) ([]*models.LogResult, error) {
  130. filter.UserID = auth.UserID(ctx)
  131. logs, err := l.DB.Logs().List(ctx, filter)
  132. if err != nil {
  133. return nil, err
  134. }
  135. taskIDs := stringset.New()
  136. itemIDs := stringset.New()
  137. projectIDs := stringset.New()
  138. for _, log := range logs {
  139. taskIDs.Add(log.TaskID)
  140. itemIDs.Add(log.ItemID)
  141. if log.SecondaryItemID != nil {
  142. itemIDs.Add(*log.SecondaryItemID)
  143. }
  144. }
  145. tasks, err := l.DB.Tasks().List(ctx, models.TaskFilter{
  146. UserID: auth.UserID(ctx),
  147. IDs: taskIDs.Strings(),
  148. })
  149. if err != nil {
  150. return nil, err
  151. }
  152. for _, task := range tasks {
  153. projectIDs.Add(task.ProjectID)
  154. }
  155. items, err := l.DB.Items().List(ctx, models.ItemFilter{
  156. UserID: auth.UserID(ctx),
  157. IDs: itemIDs.Strings(),
  158. })
  159. if err != nil {
  160. return nil, err
  161. }
  162. projects, err := l.DB.Projects().List(ctx, models.ProjectFilter{
  163. UserID: auth.UserID(ctx),
  164. IDs: projectIDs.Strings(),
  165. })
  166. if err != nil {
  167. return nil, err
  168. }
  169. results := make([]*models.LogResult, len(logs))
  170. for i, log := range logs {
  171. results[i] = &models.LogResult{
  172. Log: *log,
  173. Task: nil,
  174. }
  175. for _, task := range tasks {
  176. if task.ID == log.TaskID {
  177. results[i].Task = &models.TaskWithProject{Task: *task}
  178. break
  179. }
  180. }
  181. if results[i].Task != nil {
  182. for _, project := range projects {
  183. if project.ID == results[i].Task.ProjectID {
  184. results[i].Task.Project = project
  185. break
  186. }
  187. }
  188. }
  189. for _, item := range items {
  190. if item.ID == log.ItemID {
  191. results[i].Item = item
  192. }
  193. if log.SecondaryItemID != nil && item.ID == *log.SecondaryItemID {
  194. results[i].SecondaryItem = item
  195. }
  196. }
  197. }
  198. return results, nil
  199. }
  200. func (l *Loader) FindProject(ctx context.Context, id string) (*models.ProjectResult, error) {
  201. project, err := l.DB.Projects().Find(ctx, id)
  202. if err != nil {
  203. return nil, err
  204. }
  205. if project.UserID != auth.UserID(ctx) {
  206. return nil, slerrors.NotFound("Goal")
  207. }
  208. result := &models.ProjectResult{Project: *project}
  209. tasks, err := l.DB.Tasks().List(ctx, models.TaskFilter{
  210. UserID: auth.UserID(ctx),
  211. ProjectIDs: []string{project.ID},
  212. })
  213. taskIDs := make([]string, 0, len(tasks))
  214. itemIDs := stringset.New()
  215. for _, task := range tasks {
  216. taskIDs = append(taskIDs, task.ID)
  217. itemIDs.Add(task.ItemID)
  218. }
  219. logs, err := l.DB.Logs().List(ctx, models.LogFilter{
  220. UserID: auth.UserID(ctx),
  221. TaskIDs: taskIDs,
  222. })
  223. if err != nil {
  224. return nil, err
  225. }
  226. items, err := l.DB.Items().List(ctx, models.ItemFilter{
  227. UserID: auth.UserID(ctx),
  228. IDs: itemIDs.Strings(),
  229. })
  230. if err != nil {
  231. return nil, err
  232. }
  233. result.Tasks = make([]*models.TaskResult, len(tasks))
  234. for i, task := range tasks {
  235. result.Tasks[i] = &models.TaskResult{
  236. Logs: []*models.Log{},
  237. }
  238. result.Tasks[i].Task = *task
  239. for _, log := range logs {
  240. if log.TaskID == task.ID {
  241. result.Tasks[i].Logs = append(result.Tasks[i].Logs, log)
  242. }
  243. }
  244. for _, item := range items {
  245. if item.ID == task.ItemID {
  246. result.Tasks[i].Item = item
  247. break
  248. }
  249. }
  250. for _, log := range result.Tasks[i].Logs {
  251. result.Tasks[i].CompletedAmount += log.Amount(result.Tasks[i].ItemID)
  252. }
  253. }
  254. return result, nil
  255. }
  256. func (l *Loader) ListProjects(ctx context.Context, filter models.ProjectFilter) ([]*models.ProjectResult, error) {
  257. filter.UserID = auth.UserID(ctx)
  258. projects, err := l.DB.Projects().List(ctx, filter)
  259. if err != nil {
  260. return nil, err
  261. }
  262. projectIDs := make([]string, 0, len(projects))
  263. for _, project := range projects {
  264. projectIDs = append(projectIDs, project.ID)
  265. }
  266. tasks, links, err := l.DB.Tasks().ListWithLinks(ctx, models.TaskFilter{
  267. UserID: auth.UserID(ctx),
  268. ProjectIDs: projectIDs,
  269. })
  270. if err != nil {
  271. return nil, err
  272. }
  273. taskIDs := make([]string, 0, len(tasks))
  274. itemIDs := stringset.New()
  275. for _, task := range tasks {
  276. taskIDs = append(taskIDs, task.ID)
  277. itemIDs.Add(task.ItemID)
  278. }
  279. logs, err := l.DB.Logs().List(ctx, models.LogFilter{
  280. UserID: auth.UserID(ctx),
  281. TaskIDs: taskIDs,
  282. })
  283. if err != nil {
  284. return nil, err
  285. }
  286. items, err := l.DB.Items().List(ctx, models.ItemFilter{
  287. UserID: auth.UserID(ctx),
  288. IDs: itemIDs.Strings(),
  289. })
  290. if err != nil {
  291. return nil, err
  292. }
  293. results := make([]*models.ProjectResult, len(projects))
  294. for i, project := range projects {
  295. results[i] = &models.ProjectResult{Project: *project}
  296. results[i].Tasks = make([]*models.TaskResult, 0, 16)
  297. for _, task := range tasks {
  298. if task.ProjectID != project.ID {
  299. foundLink := false
  300. for _, link := range links {
  301. if link.TaskID == task.ID && link.ProjectID == project.ID {
  302. foundLink = true
  303. break
  304. }
  305. }
  306. if !foundLink {
  307. continue
  308. }
  309. }
  310. taskResult := &models.TaskResult{
  311. Task: *task,
  312. Logs: []*models.Log{},
  313. }
  314. for _, log := range logs {
  315. if log.TaskID == task.ID {
  316. taskResult.Logs = append(taskResult.Logs, log)
  317. }
  318. }
  319. for _, item := range items {
  320. if item.ID == task.ItemID {
  321. taskResult.Item = item
  322. break
  323. }
  324. }
  325. for _, log := range taskResult.Logs {
  326. taskResult.CompletedAmount += log.Amount(taskResult.ItemID)
  327. }
  328. results[i].Tasks = append(results[i].Tasks, taskResult)
  329. }
  330. }
  331. return results, nil
  332. }
  333. func (l *Loader) FindProjectGroup(ctx context.Context, id string) (*models.ProjectGroupResult, error) {
  334. group, err := l.DB.ProjectGroups().Find(ctx, id)
  335. if err != nil {
  336. return nil, err
  337. }
  338. projects, err := l.ListProjects(ctx, models.ProjectFilter{
  339. UserID: auth.UserID(ctx),
  340. ProjectGroupIDs: []string{group.ID},
  341. })
  342. if err != nil {
  343. return nil, err
  344. }
  345. result := &models.ProjectGroupResult{
  346. ProjectGroup: *group,
  347. Projects: projects,
  348. }
  349. result.RecountTasks()
  350. return result, nil
  351. }
  352. func (l *Loader) ListProjectGroups(ctx context.Context) ([]*models.ProjectGroupResult, error) {
  353. groups, err := l.DB.ProjectGroups().List(ctx, models.ProjectGroupFilter{UserID: auth.UserID(ctx)})
  354. if err != nil {
  355. return nil, err
  356. }
  357. ids := make([]string, 0, len(groups))
  358. for _, group := range groups {
  359. ids = append(ids, group.ID)
  360. }
  361. projects, err := l.ListProjects(ctx, models.ProjectFilter{
  362. UserID: auth.UserID(ctx),
  363. ProjectGroupIDs: ids,
  364. })
  365. results := make([]*models.ProjectGroupResult, 0, len(groups)+1)
  366. for _, group := range groups {
  367. matchingProjects := make([]*models.ProjectResult, 0, len(projects)/len(groups))
  368. for _, project := range projects {
  369. if *project.GroupID == group.ID {
  370. matchingProjects = append(matchingProjects, project)
  371. }
  372. }
  373. result := &models.ProjectGroupResult{
  374. ProjectGroup: *group,
  375. Projects: matchingProjects,
  376. }
  377. result.RecountTasks()
  378. results = append(results, result)
  379. }
  380. ungroupedProjects, err := l.ListProjects(ctx, models.ProjectFilter{
  381. UserID: auth.UserID(ctx),
  382. Ungrouped: true,
  383. })
  384. if err != nil {
  385. return nil, err
  386. }
  387. if len(ungroupedProjects) > 0 {
  388. result := &models.ProjectGroupResult{
  389. ProjectGroup: models.ProjectGroup{
  390. ID: "META_UNGROUPED",
  391. Name: "Ungrouped Projects",
  392. Abbreviation: "OTHER",
  393. CategoryNames: map[string]string{},
  394. },
  395. Projects: ungroupedProjects,
  396. }
  397. result.RecountTasks()
  398. results = append(results, result)
  399. }
  400. return results, nil
  401. }
  402. func (l *Loader) FindTask(ctx context.Context, id string) (*models.TaskResult, error) {
  403. task, err := l.DB.Tasks().Find(ctx, id)
  404. if err != nil {
  405. return nil, err
  406. }
  407. if task.UserID != auth.UserID(ctx) {
  408. return nil, slerrors.NotFound("Goal")
  409. }
  410. result := &models.TaskResult{Task: *task}
  411. result.Item, _ = l.DB.Items().Find(ctx, task.ItemID)
  412. result.Project, _ = l.DB.Projects().Find(ctx, task.ProjectID)
  413. result.Logs, err = l.DB.Logs().List(ctx, models.LogFilter{
  414. UserID: task.UserID,
  415. TaskIDs: []string{task.ID},
  416. })
  417. if err != nil {
  418. return nil, err
  419. }
  420. for _, log := range result.Logs {
  421. result.CompletedAmount += log.Amount(result.ItemID)
  422. }
  423. return result, nil
  424. }
  425. func (l *Loader) ListTasks(ctx context.Context, filter models.TaskFilter) ([]*models.TaskResult, error) {
  426. filter.UserID = auth.UserID(ctx)
  427. tasks, err := l.DB.Tasks().List(ctx, filter)
  428. if err != nil {
  429. return nil, err
  430. }
  431. if len(tasks) == 0 {
  432. return []*models.TaskResult{}, nil
  433. }
  434. taskIDs := make([]string, 0, len(tasks))
  435. itemIDs := stringset.New()
  436. projectIDs := stringset.New()
  437. for _, task := range tasks {
  438. taskIDs = append(taskIDs, task.ID)
  439. itemIDs.Add(task.ItemID)
  440. projectIDs.Add(task.ProjectID)
  441. }
  442. logs, err := l.DB.Logs().List(ctx, models.LogFilter{
  443. UserID: auth.UserID(ctx),
  444. TaskIDs: taskIDs,
  445. })
  446. if err != nil {
  447. return nil, err
  448. }
  449. items, err := l.DB.Items().List(ctx, models.ItemFilter{
  450. UserID: auth.UserID(ctx),
  451. IDs: itemIDs.Strings(),
  452. })
  453. if err != nil {
  454. return nil, err
  455. }
  456. projects, err := l.DB.Projects().List(ctx, models.ProjectFilter{
  457. UserID: auth.UserID(ctx),
  458. IDs: projectIDs.Strings(),
  459. })
  460. if err != nil {
  461. return nil, err
  462. }
  463. results := make([]*models.TaskResult, 0, len(tasks))
  464. for _, task := range tasks {
  465. result := &models.TaskResult{
  466. Task: *task,
  467. Logs: []*models.Log{},
  468. }
  469. for _, log := range logs {
  470. if log.TaskID == task.ID {
  471. result.Logs = append(result.Logs, log)
  472. }
  473. }
  474. for _, item := range items {
  475. if item.ID == task.ItemID {
  476. result.Item = item
  477. break
  478. }
  479. }
  480. for _, project := range projects {
  481. if project.ID == task.ProjectID {
  482. result.Project = project
  483. break
  484. }
  485. }
  486. for _, log := range result.Logs {
  487. result.CompletedAmount += log.Amount(result.ItemID)
  488. }
  489. results = append(results, result)
  490. }
  491. return results, nil
  492. }
  493. func (l *Loader) FindGoal(ctx context.Context, id string) (*models.GoalResult, error) {
  494. goal, err := l.DB.Goals().Find(ctx, id)
  495. if err != nil {
  496. return nil, err
  497. }
  498. if goal.UserID != auth.UserID(ctx) {
  499. return nil, slerrors.NotFound("Goal")
  500. }
  501. return l.populateGoals(ctx, goal)
  502. }
  503. func (l *Loader) ListGoals(ctx context.Context, filter models.GoalFilter) ([]*models.GoalResult, error) {
  504. filter.UserID = auth.UserID(ctx)
  505. goals, err := l.DB.Goals().List(ctx, filter)
  506. if err != nil {
  507. return nil, err
  508. }
  509. results := make([]*models.GoalResult, len(goals))
  510. eg := errgroup.Group{}
  511. for i := range results {
  512. index := i // Required to avoid race condition.
  513. eg.Go(func() error {
  514. res, err := l.populateGoals(ctx, goals[index])
  515. if err != nil {
  516. return err
  517. }
  518. results[index] = res
  519. return nil
  520. })
  521. }
  522. err = eg.Wait()
  523. if err != nil {
  524. return nil, err
  525. }
  526. return results, nil
  527. }
  528. func (l *Loader) populateGoals(ctx context.Context, goal *models.Goal) (*models.GoalResult, error) {
  529. userID := auth.UserID(ctx)
  530. result := &models.GoalResult{
  531. Goal: *goal,
  532. Group: nil,
  533. Items: nil,
  534. Logs: nil,
  535. CompletedAmount: 0,
  536. }
  537. result.Group, _ = l.DB.Groups().Find(ctx, goal.GroupID)
  538. if result.Group != nil {
  539. // Get items
  540. items, err := l.DB.Items().List(ctx, models.ItemFilter{
  541. UserID: userID,
  542. GroupIDs: []string{goal.GroupID},
  543. })
  544. if err != nil {
  545. return nil, err
  546. }
  547. itemIDs := make([]string, 0, len(items))
  548. for _, item := range items {
  549. result.Items = append(result.Items, &models.GoalResultItem{
  550. Item: *item,
  551. CompletedAmount: 0,
  552. })
  553. itemIDs = append(itemIDs, item.ID)
  554. }
  555. // Get logs
  556. logs, err := l.DB.Logs().List(ctx, models.LogFilter{
  557. UserID: userID,
  558. ItemIDs: itemIDs,
  559. MinTime: &goal.StartTime,
  560. MaxTime: &goal.EndTime,
  561. })
  562. if err != nil {
  563. return nil, err
  564. }
  565. // Get tasks
  566. taskIDs := stringset.New()
  567. for _, log := range logs {
  568. taskIDs.Add(log.TaskID)
  569. }
  570. tasks, err := l.DB.Tasks().List(ctx, models.TaskFilter{
  571. UserID: userID,
  572. IDs: taskIDs.Strings(),
  573. })
  574. if err != nil {
  575. return nil, err
  576. }
  577. projectIDs := stringset.New()
  578. for _, task := range tasks {
  579. projectIDs.Add(task.ProjectID)
  580. }
  581. projects, err := l.DB.Projects().List(ctx, models.ProjectFilter{
  582. UserID: userID,
  583. IDs: projectIDs.Strings(),
  584. })
  585. // Apply logs
  586. result.Logs = make([]*models.GoalResultLog, 0, len(logs))
  587. for _, log := range logs {
  588. resultLog := &models.GoalResultLog{
  589. LogResult: models.LogResult{Log: *log},
  590. }
  591. contributes := false
  592. for _, task := range tasks {
  593. if task.ID == log.TaskID {
  594. resultLog.Task = &models.TaskWithProject{Task: *task}
  595. for _, project := range projects {
  596. if project.ID == task.ProjectID {
  597. resultLog.Task.Project = project
  598. break
  599. }
  600. }
  601. break
  602. }
  603. }
  604. for _, item := range result.Items {
  605. amount := log.Amount(item.ID)
  606. if amount > 0 && goal.Accepts(&item.Item, &resultLog.Task.Task) {
  607. item.CompletedAmount += amount
  608. if goal.Unweighted {
  609. if item.GroupWeight > 0 {
  610. result.CompletedAmount += amount
  611. }
  612. } else {
  613. result.CompletedAmount += amount * item.GroupWeight
  614. }
  615. contributes = true
  616. } else {
  617. amount = 0
  618. }
  619. if item.ID == log.ItemID {
  620. resultLog.Item = &item.Item
  621. if amount > 0 {
  622. resultLog.ItemCounted = true
  623. }
  624. if log.SecondaryItemID == nil {
  625. break
  626. }
  627. }
  628. if log.SecondaryItemID != nil && item.ID == *log.SecondaryItemID {
  629. if amount > 0 {
  630. resultLog.SecondaryItemCounted = true
  631. }
  632. resultLog.SecondaryItem = &item.Item
  633. }
  634. }
  635. if contributes {
  636. result.Logs = append(result.Logs, resultLog)
  637. }
  638. }
  639. }
  640. return result, nil
  641. }