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

4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 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) FindTask(ctx context.Context, id string) (*models.TaskResult, error) {
  334. task, err := l.DB.Tasks().Find(ctx, id)
  335. if err != nil {
  336. return nil, err
  337. }
  338. if task.UserID != auth.UserID(ctx) {
  339. return nil, slerrors.NotFound("Goal")
  340. }
  341. result := &models.TaskResult{Task: *task}
  342. result.Item, _ = l.DB.Items().Find(ctx, task.ItemID)
  343. result.Project, _ = l.DB.Projects().Find(ctx, task.ProjectID)
  344. result.Logs, err = l.DB.Logs().List(ctx, models.LogFilter{
  345. UserID: task.UserID,
  346. TaskIDs: []string{task.ID},
  347. })
  348. if err != nil {
  349. return nil, err
  350. }
  351. for _, log := range result.Logs {
  352. result.CompletedAmount += log.Amount(result.ItemID)
  353. }
  354. return result, nil
  355. }
  356. func (l *Loader) ListTasks(ctx context.Context, filter models.TaskFilter) ([]*models.TaskResult, error) {
  357. filter.UserID = auth.UserID(ctx)
  358. tasks, err := l.DB.Tasks().List(ctx, filter)
  359. if err != nil {
  360. return nil, err
  361. }
  362. if len(tasks) == 0 {
  363. return []*models.TaskResult{}, nil
  364. }
  365. taskIDs := make([]string, 0, len(tasks))
  366. itemIDs := stringset.New()
  367. projectIDs := stringset.New()
  368. for _, task := range tasks {
  369. taskIDs = append(taskIDs, task.ID)
  370. itemIDs.Add(task.ItemID)
  371. projectIDs.Add(task.ProjectID)
  372. }
  373. logs, err := l.DB.Logs().List(ctx, models.LogFilter{
  374. UserID: auth.UserID(ctx),
  375. TaskIDs: taskIDs,
  376. })
  377. if err != nil {
  378. return nil, err
  379. }
  380. items, err := l.DB.Items().List(ctx, models.ItemFilter{
  381. UserID: auth.UserID(ctx),
  382. IDs: itemIDs.Strings(),
  383. })
  384. if err != nil {
  385. return nil, err
  386. }
  387. projects, err := l.DB.Projects().List(ctx, models.ProjectFilter{
  388. UserID: auth.UserID(ctx),
  389. IDs: projectIDs.Strings(),
  390. })
  391. if err != nil {
  392. return nil, err
  393. }
  394. results := make([]*models.TaskResult, 0, len(tasks))
  395. for _, task := range tasks {
  396. result := &models.TaskResult{
  397. Task: *task,
  398. Logs: []*models.Log{},
  399. }
  400. for _, log := range logs {
  401. if log.TaskID == task.ID {
  402. result.Logs = append(result.Logs, log)
  403. }
  404. }
  405. for _, item := range items {
  406. if item.ID == task.ItemID {
  407. result.Item = item
  408. break
  409. }
  410. }
  411. for _, project := range projects {
  412. if project.ID == task.ProjectID {
  413. result.Project = project
  414. break
  415. }
  416. }
  417. for _, log := range result.Logs {
  418. result.CompletedAmount += log.Amount(result.ItemID)
  419. }
  420. results = append(results, result)
  421. }
  422. return results, nil
  423. }
  424. func (l *Loader) FindGoal(ctx context.Context, id string) (*models.GoalResult, error) {
  425. goal, err := l.DB.Goals().Find(ctx, id)
  426. if err != nil {
  427. return nil, err
  428. }
  429. if goal.UserID != auth.UserID(ctx) {
  430. return nil, slerrors.NotFound("Goal")
  431. }
  432. return l.populateGoals(ctx, goal)
  433. }
  434. func (l *Loader) ListGoals(ctx context.Context, filter models.GoalFilter) ([]*models.GoalResult, error) {
  435. filter.UserID = auth.UserID(ctx)
  436. goals, err := l.DB.Goals().List(ctx, filter)
  437. if err != nil {
  438. return nil, err
  439. }
  440. results := make([]*models.GoalResult, len(goals))
  441. eg := errgroup.Group{}
  442. for i := range results {
  443. index := i // Required to avoid race condition.
  444. eg.Go(func() error {
  445. res, err := l.populateGoals(ctx, goals[index])
  446. if err != nil {
  447. return err
  448. }
  449. results[index] = res
  450. return nil
  451. })
  452. }
  453. err = eg.Wait()
  454. if err != nil {
  455. return nil, err
  456. }
  457. return results, nil
  458. }
  459. func (l *Loader) populateGoals(ctx context.Context, goal *models.Goal) (*models.GoalResult, error) {
  460. userID := auth.UserID(ctx)
  461. result := &models.GoalResult{
  462. Goal: *goal,
  463. Group: nil,
  464. Items: nil,
  465. Logs: nil,
  466. CompletedAmount: 0,
  467. }
  468. result.Group, _ = l.DB.Groups().Find(ctx, goal.GroupID)
  469. if result.Group != nil {
  470. // Get items
  471. items, err := l.DB.Items().List(ctx, models.ItemFilter{
  472. UserID: userID,
  473. GroupIDs: []string{goal.GroupID},
  474. })
  475. if err != nil {
  476. return nil, err
  477. }
  478. itemIDs := make([]string, 0, len(items))
  479. for _, item := range items {
  480. result.Items = append(result.Items, &models.GoalResultItem{
  481. Item: *item,
  482. CompletedAmount: 0,
  483. })
  484. itemIDs = append(itemIDs, item.ID)
  485. }
  486. // Get logs
  487. logs, err := l.DB.Logs().List(ctx, models.LogFilter{
  488. UserID: userID,
  489. ItemIDs: itemIDs,
  490. MinTime: &goal.StartTime,
  491. MaxTime: &goal.EndTime,
  492. })
  493. if err != nil {
  494. return nil, err
  495. }
  496. // Get tasks
  497. taskIDs := stringset.New()
  498. for _, log := range logs {
  499. taskIDs.Add(log.TaskID)
  500. }
  501. tasks, err := l.DB.Tasks().List(ctx, models.TaskFilter{
  502. UserID: userID,
  503. IDs: taskIDs.Strings(),
  504. })
  505. if err != nil {
  506. return nil, err
  507. }
  508. projectIDs := stringset.New()
  509. for _, task := range tasks {
  510. projectIDs.Add(task.ProjectID)
  511. }
  512. projects, err := l.DB.Projects().List(ctx, models.ProjectFilter{
  513. UserID: userID,
  514. IDs: projectIDs.Strings(),
  515. })
  516. // Apply logs
  517. result.Logs = make([]*models.GoalResultLog, 0, len(logs))
  518. for _, log := range logs {
  519. resultLog := &models.GoalResultLog{
  520. LogResult: models.LogResult{Log: *log},
  521. }
  522. contributes := false
  523. for _, task := range tasks {
  524. if task.ID == log.TaskID {
  525. resultLog.Task = &models.TaskWithProject{Task: *task}
  526. for _, project := range projects {
  527. if project.ID == task.ProjectID {
  528. resultLog.Task.Project = project
  529. break
  530. }
  531. }
  532. break
  533. }
  534. }
  535. for _, item := range result.Items {
  536. amount := log.Amount(item.ID)
  537. if amount > 0 && goal.Accepts(&item.Item, &resultLog.Task.Task) {
  538. item.CompletedAmount += amount
  539. if goal.Unweighted {
  540. if item.GroupWeight > 0 {
  541. result.CompletedAmount += amount
  542. }
  543. } else {
  544. result.CompletedAmount += amount * item.GroupWeight
  545. }
  546. contributes = true
  547. } else {
  548. amount = 0
  549. }
  550. if item.ID == log.ItemID {
  551. resultLog.Item = &item.Item
  552. if amount > 0 {
  553. resultLog.ItemCounted = true
  554. }
  555. if log.SecondaryItemID == nil {
  556. break
  557. }
  558. }
  559. if log.SecondaryItemID != nil && item.ID == *log.SecondaryItemID {
  560. if amount > 0 {
  561. resultLog.SecondaryItemCounted = true
  562. }
  563. resultLog.SecondaryItem = &item.Item
  564. }
  565. }
  566. if contributes {
  567. result.Logs = append(result.Logs, resultLog)
  568. }
  569. }
  570. }
  571. return result, nil
  572. }