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.

598 lines
13 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
  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. result.Task, _ = l.DB.Tasks().Find(ctx, id)
  112. result.Item, _ = l.DB.Items().Find(ctx, log.ItemID)
  113. if log.SecondaryItemID != nil {
  114. result.SecondaryItem, _ = l.DB.Items().Find(ctx, *log.SecondaryItemID)
  115. }
  116. return result, nil
  117. }
  118. func (l *Loader) ListLogs(ctx context.Context, filter models.LogFilter) ([]*models.LogResult, error) {
  119. filter.UserID = auth.UserID(ctx)
  120. logs, err := l.DB.Logs().List(ctx, filter)
  121. if err != nil {
  122. return nil, err
  123. }
  124. taskIDs := stringset.New()
  125. itemIDs := stringset.New()
  126. for _, log := range logs {
  127. taskIDs.Add(log.TaskID)
  128. itemIDs.Add(log.ItemID)
  129. if log.SecondaryItemID != nil {
  130. itemIDs.Add(*log.SecondaryItemID)
  131. }
  132. }
  133. tasks, err := l.DB.Tasks().List(ctx, models.TaskFilter{
  134. UserID: auth.UserID(ctx),
  135. IDs: taskIDs.Strings(),
  136. })
  137. if err != nil {
  138. return nil, err
  139. }
  140. items, err := l.DB.Items().List(ctx, models.ItemFilter{
  141. UserID: auth.UserID(ctx),
  142. IDs: itemIDs.Strings(),
  143. })
  144. if err != nil {
  145. return nil, err
  146. }
  147. results := make([]*models.LogResult, len(logs))
  148. for i, log := range logs {
  149. results[i] = &models.LogResult{
  150. Log: *log,
  151. Task: nil,
  152. }
  153. for _, task := range tasks {
  154. if task.ID == log.TaskID {
  155. results[i].Task = task
  156. break
  157. }
  158. }
  159. for _, item := range items {
  160. if item.ID == log.ItemID {
  161. results[i].Item = item
  162. }
  163. if log.SecondaryItemID != nil && item.ID == *log.SecondaryItemID {
  164. results[i].SecondaryItem = item
  165. }
  166. }
  167. }
  168. return results, nil
  169. }
  170. func (l *Loader) FindProject(ctx context.Context, id string) (*models.ProjectResult, error) {
  171. project, err := l.DB.Projects().Find(ctx, id)
  172. if err != nil {
  173. return nil, err
  174. }
  175. if project.UserID != auth.UserID(ctx) {
  176. return nil, slerrors.NotFound("Goal")
  177. }
  178. result := &models.ProjectResult{Project: *project}
  179. tasks, err := l.DB.Tasks().List(ctx, models.TaskFilter{
  180. UserID: auth.UserID(ctx),
  181. ProjectIDs: []string{project.ID},
  182. })
  183. taskIDs := make([]string, 0, len(tasks))
  184. itemIDs := stringset.New()
  185. for _, task := range tasks {
  186. taskIDs = append(taskIDs, task.ID)
  187. itemIDs.Add(task.ItemID)
  188. }
  189. logs, err := l.DB.Logs().List(ctx, models.LogFilter{
  190. UserID: auth.UserID(ctx),
  191. TaskIDs: taskIDs,
  192. })
  193. if err != nil {
  194. return nil, err
  195. }
  196. items, err := l.DB.Items().List(ctx, models.ItemFilter{
  197. UserID: auth.UserID(ctx),
  198. IDs: itemIDs.Strings(),
  199. })
  200. if err != nil {
  201. return nil, err
  202. }
  203. result.Tasks = make([]*models.TaskResult, len(tasks))
  204. for i, task := range tasks {
  205. result.Tasks[i] = &models.TaskResult{
  206. Logs: []*models.Log{},
  207. }
  208. result.Tasks[i].Task = *task
  209. for _, log := range logs {
  210. if log.TaskID == task.ID {
  211. result.Tasks[i].Logs = append(result.Tasks[i].Logs, log)
  212. }
  213. }
  214. for _, item := range items {
  215. if item.ID == task.ItemID {
  216. result.Tasks[i].Item = item
  217. break
  218. }
  219. }
  220. for _, log := range result.Tasks[i].Logs {
  221. result.Tasks[i].CompletedAmount += log.Amount(result.Tasks[i].ItemID)
  222. }
  223. }
  224. return result, nil
  225. }
  226. func (l *Loader) ListProjects(ctx context.Context, filter models.ProjectFilter) ([]*models.ProjectResult, error) {
  227. filter.UserID = auth.UserID(ctx)
  228. projects, err := l.DB.Projects().List(ctx, filter)
  229. if err != nil {
  230. return nil, err
  231. }
  232. projectIDs := make([]string, 0, len(projects))
  233. for _, project := range projects {
  234. projectIDs = append(projectIDs, project.ID)
  235. }
  236. tasks, err := l.DB.Tasks().List(ctx, models.TaskFilter{
  237. UserID: auth.UserID(ctx),
  238. ProjectIDs: projectIDs,
  239. })
  240. if err != nil {
  241. return nil, err
  242. }
  243. taskIDs := make([]string, 0, len(tasks))
  244. itemIDs := stringset.New()
  245. for _, task := range tasks {
  246. taskIDs = append(taskIDs, task.ID)
  247. itemIDs.Add(task.ItemID)
  248. }
  249. logs, err := l.DB.Logs().List(ctx, models.LogFilter{
  250. UserID: auth.UserID(ctx),
  251. TaskIDs: taskIDs,
  252. })
  253. if err != nil {
  254. return nil, err
  255. }
  256. items, err := l.DB.Items().List(ctx, models.ItemFilter{
  257. UserID: auth.UserID(ctx),
  258. IDs: itemIDs.Strings(),
  259. })
  260. if err != nil {
  261. return nil, err
  262. }
  263. results := make([]*models.ProjectResult, len(projects))
  264. for i, project := range projects {
  265. results[i] = &models.ProjectResult{Project: *project}
  266. results[i].Tasks = make([]*models.TaskResult, 0, 16)
  267. for _, task := range tasks {
  268. if task.ProjectID != project.ID {
  269. continue
  270. }
  271. taskResult := &models.TaskResult{
  272. Task: *task,
  273. Logs: []*models.Log{},
  274. }
  275. for _, log := range logs {
  276. if log.TaskID == task.ID {
  277. taskResult.Logs = append(taskResult.Logs, log)
  278. }
  279. }
  280. for _, item := range items {
  281. if item.ID == task.ItemID {
  282. taskResult.Item = item
  283. break
  284. }
  285. }
  286. for _, log := range taskResult.Logs {
  287. taskResult.CompletedAmount += log.Amount(taskResult.ItemID)
  288. }
  289. results[i].Tasks = append(results[i].Tasks, taskResult)
  290. }
  291. }
  292. return results, nil
  293. }
  294. func (l *Loader) FindTask(ctx context.Context, id string) (*models.TaskResult, error) {
  295. task, err := l.DB.Tasks().Find(ctx, id)
  296. if err != nil {
  297. return nil, err
  298. }
  299. if task.UserID != auth.UserID(ctx) {
  300. return nil, slerrors.NotFound("Goal")
  301. }
  302. result := &models.TaskResult{Task: *task}
  303. result.Item, _ = l.DB.Items().Find(ctx, task.ItemID)
  304. result.Project, _ = l.DB.Projects().Find(ctx, task.ProjectID)
  305. result.Logs, err = l.DB.Logs().List(ctx, models.LogFilter{
  306. UserID: task.UserID,
  307. TaskIDs: []string{task.ID},
  308. })
  309. if err != nil {
  310. return nil, err
  311. }
  312. for _, log := range result.Logs {
  313. result.CompletedAmount += log.Amount(result.ItemID)
  314. }
  315. return result, nil
  316. }
  317. func (l *Loader) ListTasks(ctx context.Context, filter models.TaskFilter) ([]*models.TaskResult, error) {
  318. filter.UserID = auth.UserID(ctx)
  319. tasks, err := l.DB.Tasks().List(ctx, filter)
  320. if err != nil {
  321. return nil, err
  322. }
  323. if len(tasks) == 0 {
  324. return []*models.TaskResult{}, nil
  325. }
  326. taskIDs := make([]string, 0, len(tasks))
  327. itemIDs := stringset.New()
  328. projectIDs := stringset.New()
  329. for _, task := range tasks {
  330. taskIDs = append(taskIDs, task.ID)
  331. itemIDs.Add(task.ItemID)
  332. projectIDs.Add(task.ProjectID)
  333. }
  334. logs, err := l.DB.Logs().List(ctx, models.LogFilter{
  335. UserID: auth.UserID(ctx),
  336. TaskIDs: taskIDs,
  337. })
  338. if err != nil {
  339. return nil, err
  340. }
  341. items, err := l.DB.Items().List(ctx, models.ItemFilter{
  342. UserID: auth.UserID(ctx),
  343. IDs: itemIDs.Strings(),
  344. })
  345. if err != nil {
  346. return nil, err
  347. }
  348. projects, err := l.DB.Projects().List(ctx, models.ProjectFilter{
  349. UserID: auth.UserID(ctx),
  350. IDs: projectIDs.Strings(),
  351. })
  352. if err != nil {
  353. return nil, err
  354. }
  355. results := make([]*models.TaskResult, 0, len(tasks))
  356. for _, task := range tasks {
  357. result := &models.TaskResult{
  358. Task: *task,
  359. Logs: []*models.Log{},
  360. }
  361. for _, log := range logs {
  362. if log.TaskID == task.ID {
  363. result.Logs = append(result.Logs, log)
  364. }
  365. }
  366. for _, item := range items {
  367. if item.ID == task.ItemID {
  368. result.Item = item
  369. break
  370. }
  371. }
  372. for _, project := range projects {
  373. if project.ID == task.ProjectID {
  374. result.Project = project
  375. break
  376. }
  377. }
  378. for _, log := range result.Logs {
  379. result.CompletedAmount += log.Amount(result.ItemID)
  380. }
  381. results = append(results, result)
  382. }
  383. return results, nil
  384. }
  385. func (l *Loader) FindGoal(ctx context.Context, id string) (*models.GoalResult, error) {
  386. goal, err := l.DB.Goals().Find(ctx, id)
  387. if err != nil {
  388. return nil, err
  389. }
  390. if goal.UserID != auth.UserID(ctx) {
  391. return nil, slerrors.NotFound("Goal")
  392. }
  393. return l.populateGoals(ctx, goal)
  394. }
  395. func (l *Loader) ListGoals(ctx context.Context, filter models.GoalFilter) ([]*models.GoalResult, error) {
  396. filter.UserID = auth.UserID(ctx)
  397. goals, err := l.DB.Goals().List(ctx, filter)
  398. if err != nil {
  399. return nil, err
  400. }
  401. results := make([]*models.GoalResult, len(goals))
  402. eg := errgroup.Group{}
  403. for i := range results {
  404. index := i // Required to avoid race condition.
  405. eg.Go(func() error {
  406. res, err := l.populateGoals(ctx, goals[index])
  407. if err != nil {
  408. return err
  409. }
  410. results[index] = res
  411. return nil
  412. })
  413. }
  414. err = eg.Wait()
  415. if err != nil {
  416. return nil, err
  417. }
  418. return results, nil
  419. }
  420. func (l *Loader) populateGoals(ctx context.Context, goal *models.Goal) (*models.GoalResult, error) {
  421. userID := auth.UserID(ctx)
  422. result := &models.GoalResult{
  423. Goal: *goal,
  424. Group: nil,
  425. Items: nil,
  426. Logs: nil,
  427. CompletedAmount: 0,
  428. }
  429. result.Group, _ = l.DB.Groups().Find(ctx, goal.GroupID)
  430. if result.Group != nil {
  431. // Get items
  432. items, err := l.DB.Items().List(ctx, models.ItemFilter{
  433. UserID: userID,
  434. GroupIDs: []string{goal.GroupID},
  435. })
  436. if err != nil {
  437. return nil, err
  438. }
  439. itemIDs := make([]string, 0, len(items))
  440. for _, item := range items {
  441. result.Items = append(result.Items, &models.GoalResultItem{
  442. Item: *item,
  443. CompletedAmount: 0,
  444. })
  445. itemIDs = append(itemIDs, item.ID)
  446. }
  447. // Get logs
  448. logs, err := l.DB.Logs().List(ctx, models.LogFilter{
  449. UserID: userID,
  450. ItemIDs: itemIDs,
  451. MinTime: &goal.StartTime,
  452. MaxTime: &goal.EndTime,
  453. })
  454. if err != nil {
  455. return nil, err
  456. }
  457. // Get tasks
  458. taskIDs := stringset.New()
  459. for _, log := range logs {
  460. taskIDs.Add(log.TaskID)
  461. }
  462. tasks, err := l.DB.Tasks().List(ctx, models.TaskFilter{
  463. UserID: userID,
  464. IDs: taskIDs.Strings(),
  465. })
  466. if err != nil {
  467. return nil, err
  468. }
  469. // Apply logs
  470. result.Logs = make([]*models.GoalResultLog, 0, len(logs))
  471. for _, log := range logs {
  472. resultLog := &models.GoalResultLog{
  473. LogResult: models.LogResult{Log: *log},
  474. }
  475. contributes := false
  476. for _, task := range tasks {
  477. if task.ID == log.TaskID {
  478. resultLog.Task = task
  479. break
  480. }
  481. }
  482. for _, item := range result.Items {
  483. amount := log.Amount(item.ID)
  484. if amount > 0 && goal.Accepts(&item.Item, resultLog.Task) {
  485. item.CompletedAmount += amount
  486. if goal.Unweighted {
  487. if item.GroupWeight > 0 {
  488. result.CompletedAmount += amount
  489. }
  490. } else {
  491. result.CompletedAmount += amount * item.GroupWeight
  492. }
  493. contributes = true
  494. } else {
  495. amount = 0
  496. }
  497. if item.ID == log.ItemID {
  498. resultLog.Item = &item.Item
  499. if amount > 0 {
  500. resultLog.ItemCounted = true
  501. }
  502. if log.SecondaryItemID == nil {
  503. break
  504. }
  505. }
  506. if log.SecondaryItemID != nil && item.ID == *log.SecondaryItemID {
  507. if amount > 0 {
  508. resultLog.SecondaryItemCounted = true
  509. }
  510. resultLog.SecondaryItem = &item.Item
  511. }
  512. }
  513. if contributes {
  514. result.Logs = append(result.Logs, resultLog)
  515. }
  516. }
  517. }
  518. return result, nil
  519. }