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.

573 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
  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. result.Tasks[i].CompletedAmount = len(result.Tasks[i].Logs)
  221. }
  222. return result, nil
  223. }
  224. func (l *Loader) ListProjects(ctx context.Context, filter models.ProjectFilter) ([]*models.ProjectResult, error) {
  225. filter.UserID = auth.UserID(ctx)
  226. projects, err := l.DB.Projects().List(ctx, filter)
  227. if err != nil {
  228. return nil, err
  229. }
  230. projectIDs := make([]string, 0, len(projects))
  231. for _, project := range projects {
  232. projectIDs = append(projectIDs, project.ID)
  233. }
  234. tasks, err := l.DB.Tasks().List(ctx, models.TaskFilter{
  235. UserID: auth.UserID(ctx),
  236. ProjectIDs: projectIDs,
  237. })
  238. if err != nil {
  239. return nil, err
  240. }
  241. taskIDs := make([]string, 0, len(tasks))
  242. itemIDs := stringset.New()
  243. for _, task := range tasks {
  244. taskIDs = append(taskIDs, task.ID)
  245. itemIDs.Add(task.ItemID)
  246. }
  247. logs, err := l.DB.Logs().List(ctx, models.LogFilter{
  248. UserID: auth.UserID(ctx),
  249. TaskIDs: taskIDs,
  250. })
  251. if err != nil {
  252. return nil, err
  253. }
  254. items, err := l.DB.Items().List(ctx, models.ItemFilter{
  255. UserID: auth.UserID(ctx),
  256. IDs: itemIDs.Strings(),
  257. })
  258. if err != nil {
  259. return nil, err
  260. }
  261. results := make([]*models.ProjectResult, len(projects))
  262. for i, project := range projects {
  263. results[i] = &models.ProjectResult{Project: *project}
  264. results[i].Tasks = make([]*models.TaskResult, 0, 16)
  265. for _, task := range tasks {
  266. if task.ProjectID != project.ID {
  267. continue
  268. }
  269. taskResult := &models.TaskResult{
  270. Task: *task,
  271. Logs: []*models.Log{},
  272. }
  273. for _, log := range logs {
  274. if log.TaskID == task.ID {
  275. taskResult.Logs = append(taskResult.Logs, log)
  276. }
  277. }
  278. for _, item := range items {
  279. if item.ID == task.ItemID {
  280. taskResult.Item = item
  281. break
  282. }
  283. }
  284. taskResult.CompletedAmount = len(taskResult.Logs)
  285. results[i].Tasks = append(results[i].Tasks, taskResult)
  286. }
  287. }
  288. return results, nil
  289. }
  290. func (l *Loader) FindTask(ctx context.Context, id string) (*models.TaskResult, error) {
  291. task, err := l.DB.Tasks().Find(ctx, id)
  292. if err != nil {
  293. return nil, err
  294. }
  295. if task.UserID != auth.UserID(ctx) {
  296. return nil, slerrors.NotFound("Goal")
  297. }
  298. result := &models.TaskResult{Task: *task}
  299. result.Item, _ = l.DB.Items().Find(ctx, task.ItemID)
  300. result.Project, _ = l.DB.Projects().Find(ctx, task.ProjectID)
  301. result.Logs, err = l.DB.Logs().List(ctx, models.LogFilter{
  302. UserID: task.UserID,
  303. TaskIDs: []string{task.ID},
  304. })
  305. if err != nil {
  306. return nil, err
  307. }
  308. result.CompletedAmount = len(result.Logs)
  309. return result, nil
  310. }
  311. func (l *Loader) ListTasks(ctx context.Context, filter models.TaskFilter) ([]*models.TaskResult, error) {
  312. filter.UserID = auth.UserID(ctx)
  313. tasks, err := l.DB.Tasks().List(ctx, filter)
  314. if err != nil {
  315. return nil, err
  316. }
  317. if len(tasks) == 0 {
  318. return []*models.TaskResult{}, nil
  319. }
  320. taskIDs := make([]string, 0, len(tasks))
  321. itemIDs := stringset.New()
  322. projectIDs := stringset.New()
  323. for _, task := range tasks {
  324. taskIDs = append(taskIDs, task.ID)
  325. itemIDs.Add(task.ItemID)
  326. projectIDs.Add(task.ProjectID)
  327. }
  328. logs, err := l.DB.Logs().List(ctx, models.LogFilter{
  329. UserID: auth.UserID(ctx),
  330. TaskIDs: taskIDs,
  331. })
  332. if err != nil {
  333. return nil, err
  334. }
  335. items, err := l.DB.Items().List(ctx, models.ItemFilter{
  336. UserID: auth.UserID(ctx),
  337. IDs: itemIDs.Strings(),
  338. })
  339. if err != nil {
  340. return nil, err
  341. }
  342. projects, err := l.DB.Projects().List(ctx, models.ProjectFilter{
  343. UserID: auth.UserID(ctx),
  344. IDs: projectIDs.Strings(),
  345. })
  346. if err != nil {
  347. return nil, err
  348. }
  349. results := make([]*models.TaskResult, 0, len(tasks))
  350. for _, task := range tasks {
  351. result := &models.TaskResult{
  352. Task: *task,
  353. Logs: []*models.Log{},
  354. }
  355. for _, log := range logs {
  356. if log.TaskID == task.ID {
  357. result.Logs = append(result.Logs, log)
  358. }
  359. }
  360. for _, item := range items {
  361. if item.ID == task.ItemID {
  362. result.Item = item
  363. break
  364. }
  365. }
  366. for _, project := range projects {
  367. if project.ID == task.ProjectID {
  368. result.Project = project
  369. break
  370. }
  371. }
  372. result.CompletedAmount = len(result.Logs)
  373. results = append(results, result)
  374. }
  375. return results, nil
  376. }
  377. func (l *Loader) FindGoal(ctx context.Context, id string) (*models.GoalResult, error) {
  378. goal, err := l.DB.Goals().Find(ctx, id)
  379. if err != nil {
  380. return nil, err
  381. }
  382. if goal.UserID != auth.UserID(ctx) {
  383. return nil, slerrors.NotFound("Goal")
  384. }
  385. return l.populateGoals(ctx, goal)
  386. }
  387. func (l *Loader) ListGoals(ctx context.Context, filter models.GoalFilter) ([]*models.GoalResult, error) {
  388. filter.UserID = auth.UserID(ctx)
  389. goals, err := l.DB.Goals().List(ctx, filter)
  390. if err != nil {
  391. return nil, err
  392. }
  393. results := make([]*models.GoalResult, len(goals))
  394. eg := errgroup.Group{}
  395. for i := range results {
  396. index := i // Required to avoid race condition.
  397. eg.Go(func() error {
  398. res, err := l.populateGoals(ctx, goals[index])
  399. if err != nil {
  400. return err
  401. }
  402. results[index] = res
  403. return nil
  404. })
  405. }
  406. err = eg.Wait()
  407. if err != nil {
  408. return nil, err
  409. }
  410. return results, nil
  411. }
  412. func (l *Loader) populateGoals(ctx context.Context, goal *models.Goal) (*models.GoalResult, error) {
  413. userID := auth.UserID(ctx)
  414. result := &models.GoalResult{
  415. Goal: *goal,
  416. Group: nil,
  417. Items: nil,
  418. Logs: nil,
  419. CompletedAmount: 0,
  420. }
  421. result.Group, _ = l.DB.Groups().Find(ctx, goal.GroupID)
  422. if result.Group != nil {
  423. // Get items
  424. items, err := l.DB.Items().List(ctx, models.ItemFilter{
  425. UserID: userID,
  426. GroupIDs: []string{goal.GroupID},
  427. })
  428. if err != nil {
  429. return nil, err
  430. }
  431. itemIDs := make([]string, 0, len(items))
  432. for _, item := range items {
  433. result.Items = append(result.Items, &models.GoalResultItem{
  434. Item: *item,
  435. CompletedAmount: 0,
  436. })
  437. itemIDs = append(itemIDs, item.ID)
  438. }
  439. // Get logs
  440. logs, err := l.DB.Logs().List(ctx, models.LogFilter{
  441. UserID: userID,
  442. ItemIDs: itemIDs,
  443. MinTime: &goal.StartTime,
  444. MaxTime: &goal.EndTime,
  445. })
  446. if err != nil {
  447. return nil, err
  448. }
  449. // Get tasks
  450. taskIDs := stringset.New()
  451. for _, log := range logs {
  452. taskIDs.Add(log.TaskID)
  453. }
  454. tasks, err := l.DB.Tasks().List(ctx, models.TaskFilter{
  455. UserID: userID,
  456. IDs: taskIDs.Strings(),
  457. })
  458. if err != nil {
  459. return nil, err
  460. }
  461. // Apply logs
  462. result.Logs = make([]*models.LogResult, 0, len(logs))
  463. for _, log := range logs {
  464. resultLog := &models.LogResult{
  465. Log: *log,
  466. }
  467. for _, task := range tasks {
  468. if task.ID == log.TaskID {
  469. resultLog.Task = task
  470. break
  471. }
  472. }
  473. for _, item := range result.Items {
  474. amount := log.Amount(item.ID)
  475. if amount > 0 && (goal.ItemID == nil || *goal.ItemID == item.ID) {
  476. item.CompletedAmount += amount
  477. if goal.Unweighted {
  478. result.CompletedAmount += amount
  479. } else {
  480. result.CompletedAmount += amount * item.GroupWeight
  481. }
  482. }
  483. if item.ID == log.ItemID {
  484. resultLog.Item = &item.Item
  485. if log.SecondaryItemID == nil {
  486. break
  487. }
  488. }
  489. if log.SecondaryItemID != nil && item.ID == *log.SecondaryItemID {
  490. resultLog.SecondaryItem = &item.Item
  491. }
  492. }
  493. result.Logs = append(result.Logs, resultLog)
  494. }
  495. }
  496. return result, nil
  497. }