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.

609 lines
14 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
  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, links, err := l.DB.Tasks().ListWithLinks(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. foundLink := false
  270. for _, link := range links {
  271. if link.TaskID == task.ID && link.ProjectID == project.ID {
  272. foundLink = true
  273. break
  274. }
  275. }
  276. if !foundLink {
  277. continue
  278. }
  279. }
  280. taskResult := &models.TaskResult{
  281. Task: *task,
  282. Logs: []*models.Log{},
  283. }
  284. for _, log := range logs {
  285. if log.TaskID == task.ID {
  286. taskResult.Logs = append(taskResult.Logs, log)
  287. }
  288. }
  289. for _, item := range items {
  290. if item.ID == task.ItemID {
  291. taskResult.Item = item
  292. break
  293. }
  294. }
  295. for _, log := range taskResult.Logs {
  296. taskResult.CompletedAmount += log.Amount(taskResult.ItemID)
  297. }
  298. results[i].Tasks = append(results[i].Tasks, taskResult)
  299. }
  300. }
  301. return results, nil
  302. }
  303. func (l *Loader) FindTask(ctx context.Context, id string) (*models.TaskResult, error) {
  304. task, err := l.DB.Tasks().Find(ctx, id)
  305. if err != nil {
  306. return nil, err
  307. }
  308. if task.UserID != auth.UserID(ctx) {
  309. return nil, slerrors.NotFound("Goal")
  310. }
  311. result := &models.TaskResult{Task: *task}
  312. result.Item, _ = l.DB.Items().Find(ctx, task.ItemID)
  313. result.Project, _ = l.DB.Projects().Find(ctx, task.ProjectID)
  314. result.Logs, err = l.DB.Logs().List(ctx, models.LogFilter{
  315. UserID: task.UserID,
  316. TaskIDs: []string{task.ID},
  317. })
  318. if err != nil {
  319. return nil, err
  320. }
  321. for _, log := range result.Logs {
  322. result.CompletedAmount += log.Amount(result.ItemID)
  323. }
  324. return result, nil
  325. }
  326. func (l *Loader) ListTasks(ctx context.Context, filter models.TaskFilter) ([]*models.TaskResult, error) {
  327. filter.UserID = auth.UserID(ctx)
  328. tasks, err := l.DB.Tasks().List(ctx, filter)
  329. if err != nil {
  330. return nil, err
  331. }
  332. if len(tasks) == 0 {
  333. return []*models.TaskResult{}, nil
  334. }
  335. taskIDs := make([]string, 0, len(tasks))
  336. itemIDs := stringset.New()
  337. projectIDs := stringset.New()
  338. for _, task := range tasks {
  339. taskIDs = append(taskIDs, task.ID)
  340. itemIDs.Add(task.ItemID)
  341. projectIDs.Add(task.ProjectID)
  342. }
  343. logs, err := l.DB.Logs().List(ctx, models.LogFilter{
  344. UserID: auth.UserID(ctx),
  345. TaskIDs: taskIDs,
  346. })
  347. if err != nil {
  348. return nil, err
  349. }
  350. items, err := l.DB.Items().List(ctx, models.ItemFilter{
  351. UserID: auth.UserID(ctx),
  352. IDs: itemIDs.Strings(),
  353. })
  354. if err != nil {
  355. return nil, err
  356. }
  357. projects, err := l.DB.Projects().List(ctx, models.ProjectFilter{
  358. UserID: auth.UserID(ctx),
  359. IDs: projectIDs.Strings(),
  360. })
  361. if err != nil {
  362. return nil, err
  363. }
  364. results := make([]*models.TaskResult, 0, len(tasks))
  365. for _, task := range tasks {
  366. result := &models.TaskResult{
  367. Task: *task,
  368. Logs: []*models.Log{},
  369. }
  370. for _, log := range logs {
  371. if log.TaskID == task.ID {
  372. result.Logs = append(result.Logs, log)
  373. }
  374. }
  375. for _, item := range items {
  376. if item.ID == task.ItemID {
  377. result.Item = item
  378. break
  379. }
  380. }
  381. for _, project := range projects {
  382. if project.ID == task.ProjectID {
  383. result.Project = project
  384. break
  385. }
  386. }
  387. for _, log := range result.Logs {
  388. result.CompletedAmount += log.Amount(result.ItemID)
  389. }
  390. results = append(results, result)
  391. }
  392. return results, nil
  393. }
  394. func (l *Loader) FindGoal(ctx context.Context, id string) (*models.GoalResult, error) {
  395. goal, err := l.DB.Goals().Find(ctx, id)
  396. if err != nil {
  397. return nil, err
  398. }
  399. if goal.UserID != auth.UserID(ctx) {
  400. return nil, slerrors.NotFound("Goal")
  401. }
  402. return l.populateGoals(ctx, goal)
  403. }
  404. func (l *Loader) ListGoals(ctx context.Context, filter models.GoalFilter) ([]*models.GoalResult, error) {
  405. filter.UserID = auth.UserID(ctx)
  406. goals, err := l.DB.Goals().List(ctx, filter)
  407. if err != nil {
  408. return nil, err
  409. }
  410. results := make([]*models.GoalResult, len(goals))
  411. eg := errgroup.Group{}
  412. for i := range results {
  413. index := i // Required to avoid race condition.
  414. eg.Go(func() error {
  415. res, err := l.populateGoals(ctx, goals[index])
  416. if err != nil {
  417. return err
  418. }
  419. results[index] = res
  420. return nil
  421. })
  422. }
  423. err = eg.Wait()
  424. if err != nil {
  425. return nil, err
  426. }
  427. return results, nil
  428. }
  429. func (l *Loader) populateGoals(ctx context.Context, goal *models.Goal) (*models.GoalResult, error) {
  430. userID := auth.UserID(ctx)
  431. result := &models.GoalResult{
  432. Goal: *goal,
  433. Group: nil,
  434. Items: nil,
  435. Logs: nil,
  436. CompletedAmount: 0,
  437. }
  438. result.Group, _ = l.DB.Groups().Find(ctx, goal.GroupID)
  439. if result.Group != nil {
  440. // Get items
  441. items, err := l.DB.Items().List(ctx, models.ItemFilter{
  442. UserID: userID,
  443. GroupIDs: []string{goal.GroupID},
  444. })
  445. if err != nil {
  446. return nil, err
  447. }
  448. itemIDs := make([]string, 0, len(items))
  449. for _, item := range items {
  450. result.Items = append(result.Items, &models.GoalResultItem{
  451. Item: *item,
  452. CompletedAmount: 0,
  453. })
  454. itemIDs = append(itemIDs, item.ID)
  455. }
  456. // Get logs
  457. logs, err := l.DB.Logs().List(ctx, models.LogFilter{
  458. UserID: userID,
  459. ItemIDs: itemIDs,
  460. MinTime: &goal.StartTime,
  461. MaxTime: &goal.EndTime,
  462. })
  463. if err != nil {
  464. return nil, err
  465. }
  466. // Get tasks
  467. taskIDs := stringset.New()
  468. for _, log := range logs {
  469. taskIDs.Add(log.TaskID)
  470. }
  471. tasks, err := l.DB.Tasks().List(ctx, models.TaskFilter{
  472. UserID: userID,
  473. IDs: taskIDs.Strings(),
  474. })
  475. if err != nil {
  476. return nil, err
  477. }
  478. // Apply logs
  479. result.Logs = make([]*models.GoalResultLog, 0, len(logs))
  480. for _, log := range logs {
  481. resultLog := &models.GoalResultLog{
  482. LogResult: models.LogResult{Log: *log},
  483. }
  484. contributes := false
  485. for _, task := range tasks {
  486. if task.ID == log.TaskID {
  487. resultLog.Task = task
  488. break
  489. }
  490. }
  491. for _, item := range result.Items {
  492. amount := log.Amount(item.ID)
  493. if amount > 0 && goal.Accepts(&item.Item, resultLog.Task) {
  494. item.CompletedAmount += amount
  495. if goal.Unweighted {
  496. if item.GroupWeight > 0 {
  497. result.CompletedAmount += amount
  498. }
  499. } else {
  500. result.CompletedAmount += amount * item.GroupWeight
  501. }
  502. contributes = true
  503. } else {
  504. amount = 0
  505. }
  506. if item.ID == log.ItemID {
  507. resultLog.Item = &item.Item
  508. if amount > 0 {
  509. resultLog.ItemCounted = true
  510. }
  511. if log.SecondaryItemID == nil {
  512. break
  513. }
  514. }
  515. if log.SecondaryItemID != nil && item.ID == *log.SecondaryItemID {
  516. if amount > 0 {
  517. resultLog.SecondaryItemCounted = true
  518. }
  519. resultLog.SecondaryItem = &item.Item
  520. }
  521. }
  522. if contributes {
  523. result.Logs = append(result.Logs, resultLog)
  524. }
  525. }
  526. }
  527. return result, nil
  528. }