Loggest thy stuff
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.

374 lines
8.7 KiB

  1. package mysql
  2. import (
  3. "context"
  4. "database/sql"
  5. "git.aiterp.net/stufflog3/stufflog3-api/internal/database/mysql/mysqlcore"
  6. "git.aiterp.net/stufflog3/stufflog3-api/internal/models"
  7. "git.aiterp.net/stufflog3/stufflog3-api/internal/slerrors"
  8. "git.aiterp.net/stufflog3/stufflog3-api/internal/sqltypes"
  9. "strings"
  10. "time"
  11. )
  12. type itemRepository struct {
  13. db *sql.DB
  14. q *mysqlcore.Queries
  15. scopeID int
  16. }
  17. func (r *itemRepository) Find(ctx context.Context, id int) (*models.Item, error) {
  18. res, err := r.q.GetItem(ctx, id)
  19. if err != nil {
  20. if err == sql.ErrNoRows {
  21. return nil, slerrors.NotFound("Item")
  22. }
  23. return nil, err
  24. }
  25. if res.ScopeID != r.scopeID {
  26. return nil, slerrors.NotFound("Item")
  27. }
  28. item := r.resToItem(mysqlcore.ListItemsAcquiredBetweenRow(res))
  29. stats, _ := r.q.ListItemStatProgress(ctx, id)
  30. for _, stat := range stats {
  31. item.Stats = append(item.Stats, models.StatProgressEntry{
  32. StatEntry: models.StatEntry{
  33. ID: int(stat.ID.Int32),
  34. Name: stat.Name.String,
  35. Weight: stat.Weight,
  36. },
  37. Acquired: stat.Acquired,
  38. Required: stat.Required,
  39. })
  40. }
  41. return &item, nil
  42. }
  43. func (r *itemRepository) ListCreated(ctx context.Context, from, to time.Time) ([]models.Item, error) {
  44. rows, err := r.q.ListItemsCreatedBetween(ctx, mysqlcore.ListItemsCreatedBetweenParams{
  45. CreatedTime: from,
  46. CreatedTime_2: to,
  47. ScopeID: r.scopeID,
  48. })
  49. if err != nil {
  50. return nil, err
  51. }
  52. items := make([]models.Item, 0, len(rows))
  53. for _, row := range rows {
  54. items = append(items, r.resToItem(mysqlcore.ListItemsAcquiredBetweenRow(row)))
  55. }
  56. err = r.fillStats(ctx, items)
  57. if err != nil {
  58. return nil, err
  59. }
  60. return items, nil
  61. }
  62. func (r *itemRepository) ListAcquired(ctx context.Context, from, to time.Time) ([]models.Item, error) {
  63. rows, err := r.q.ListItemsAcquiredBetween(ctx, mysqlcore.ListItemsAcquiredBetweenParams{
  64. AcquiredTime: sql.NullTime{Valid: true, Time: from},
  65. AcquiredTime_2: sql.NullTime{Valid: true, Time: to},
  66. ScopeID: r.scopeID,
  67. })
  68. if err != nil {
  69. return nil, err
  70. }
  71. items := make([]models.Item, 0, len(rows))
  72. for _, row := range rows {
  73. items = append(items, r.resToItem(row))
  74. }
  75. err = r.fillStats(ctx, items)
  76. if err != nil {
  77. return nil, err
  78. }
  79. return items, nil
  80. }
  81. func (r *itemRepository) ListScheduled(ctx context.Context, from, to models.Date) ([]models.Item, error) {
  82. rows, err := r.q.ListItemsScheduledBetween(ctx, mysqlcore.ListItemsScheduledBetweenParams{
  83. ScheduledDate: sqltypes.NullDate{Valid: true, Date: from},
  84. ScheduledDate_2: sqltypes.NullDate{Valid: true, Date: to},
  85. ScopeID: r.scopeID,
  86. })
  87. if err != nil {
  88. return nil, err
  89. }
  90. items := make([]models.Item, 0, len(rows))
  91. for _, row := range rows {
  92. items = append(items, r.resToItem(mysqlcore.ListItemsAcquiredBetweenRow(row)))
  93. }
  94. err = r.fillStats(ctx, items)
  95. if err != nil {
  96. return nil, err
  97. }
  98. return items, nil
  99. }
  100. func (r *itemRepository) ListLoose(ctx context.Context, from, to time.Time) ([]models.Item, error) {
  101. rows, err := r.q.ListItemsLooseBetween(ctx, mysqlcore.ListItemsLooseBetweenParams{
  102. CreatedTime: from,
  103. CreatedTime_2: to,
  104. ScopeID: r.scopeID,
  105. })
  106. if err != nil {
  107. return nil, err
  108. }
  109. items := make([]models.Item, 0, len(rows))
  110. for _, row := range rows {
  111. items = append(items, r.resToItem(mysqlcore.ListItemsAcquiredBetweenRow(row)))
  112. }
  113. err = r.fillStats(ctx, items)
  114. if err != nil {
  115. return nil, err
  116. }
  117. return items, nil
  118. }
  119. func (r *itemRepository) Create(ctx context.Context, item models.Item) (*models.Item, error) {
  120. item.Stats = append(item.Stats[:0:0], item.Stats...)
  121. tx, err := r.db.BeginTx(ctx, nil)
  122. if err != nil {
  123. return nil, err
  124. }
  125. defer tx.Rollback()
  126. q := r.q.WithTx(tx)
  127. if item.ProjectRequirementID != nil {
  128. pr, err := q.GetProjectRequirement(ctx, *item.ProjectRequirementID)
  129. if err != nil || pr.ScopeID != r.scopeID {
  130. return nil, slerrors.NotFound("Project requirement")
  131. }
  132. }
  133. prID, acqTime, schDate := r.generateNullables(item)
  134. res, err := q.InsertItem(ctx, mysqlcore.InsertItemParams{
  135. ScopeID: r.scopeID,
  136. ProjectRequirementID: prID,
  137. Name: item.Name,
  138. Description: item.Description,
  139. CreatedTime: time.Now(),
  140. CreatedUserID: item.OwnerID,
  141. AcquiredTime: acqTime,
  142. ScheduledDate: schDate,
  143. })
  144. if err != nil {
  145. return nil, err
  146. }
  147. id, err := res.LastInsertId()
  148. if err != nil {
  149. return nil, err
  150. }
  151. for _, stat := range item.Stats {
  152. err = q.ReplaceItemStatProgress(ctx, mysqlcore.ReplaceItemStatProgressParams{
  153. ItemID: int(id),
  154. StatID: stat.ID,
  155. Acquired: stat.Acquired,
  156. Required: stat.Required,
  157. })
  158. if err != nil {
  159. return nil, err
  160. }
  161. }
  162. err = tx.Commit()
  163. if err != nil {
  164. return nil, err
  165. }
  166. return r.Find(ctx, int(id))
  167. }
  168. func (r *itemRepository) generateNullables(item models.Item) (prID sql.NullInt32, acqTime sql.NullTime, schDate sqltypes.NullDate) {
  169. if item.ProjectRequirementID != nil {
  170. prID.Valid = true
  171. prID.Int32 = int32(*item.ProjectRequirementID)
  172. }
  173. if item.AcquiredTime != nil {
  174. acqTime.Valid = true
  175. acqTime.Time = *item.AcquiredTime
  176. }
  177. if item.ScheduledDate != nil {
  178. schDate.Valid = true
  179. schDate.Date = *item.ScheduledDate
  180. }
  181. return
  182. }
  183. func (r *itemRepository) Update(ctx context.Context, item models.Item, update models.ItemUpdate) (*models.Item, error) {
  184. tx, err := r.db.BeginTx(ctx, nil)
  185. if err != nil {
  186. return nil, err
  187. }
  188. defer tx.Rollback()
  189. q := r.q.WithTx(tx)
  190. if update.ProjectRequirementID != nil {
  191. pr, err := q.GetProjectRequirement(ctx, *update.ProjectRequirementID)
  192. if err != nil || pr.ScopeID != r.scopeID {
  193. return nil, slerrors.NotFound("Project requirement")
  194. }
  195. }
  196. item.ApplyUpdate(update)
  197. prID, acqTime, schDate := r.generateNullables(item)
  198. err = q.UpdateItem(ctx, mysqlcore.UpdateItemParams{
  199. ID: item.ID,
  200. ProjectRequirementID: prID,
  201. Name: item.Name,
  202. Description: item.Description,
  203. AcquiredTime: acqTime,
  204. ScheduledDate: schDate,
  205. CreatedUserID: item.OwnerID,
  206. })
  207. if err != nil {
  208. return nil, err
  209. }
  210. for _, stat := range update.Stats {
  211. if stat.Acquired == 0 && stat.Required == 0 {
  212. err = q.DeleteItemStatProgress(ctx, mysqlcore.DeleteItemStatProgressParams{
  213. ItemID: item.ID,
  214. StatID: stat.ID,
  215. })
  216. } else {
  217. err = q.ReplaceItemStatProgress(ctx, mysqlcore.ReplaceItemStatProgressParams{
  218. ItemID: item.ID,
  219. StatID: stat.ID,
  220. Acquired: stat.Acquired,
  221. Required: stat.Required,
  222. })
  223. }
  224. if err != nil {
  225. return nil, err
  226. }
  227. }
  228. err = tx.Commit()
  229. if err != nil {
  230. return nil, err
  231. }
  232. return r.Find(ctx, item.ID)
  233. }
  234. func (r *itemRepository) Delete(ctx context.Context, item models.Item) error {
  235. tx, err := r.db.BeginTx(ctx, nil)
  236. if err != nil {
  237. return err
  238. }
  239. defer tx.Rollback()
  240. q := r.q.WithTx(tx)
  241. err = q.DeleteItem(ctx, item.ID)
  242. if err != nil {
  243. return err
  244. }
  245. err = q.ClearItemStatProgress(ctx, item.ID)
  246. if err != nil {
  247. return err
  248. }
  249. return tx.Commit()
  250. }
  251. func (r *itemRepository) resToItem(res mysqlcore.ListItemsAcquiredBetweenRow) models.Item {
  252. item := models.Item{
  253. ID: res.ID,
  254. ScopeID: res.ScopeID,
  255. OwnerID: res.CreatedUserID,
  256. Name: res.Name,
  257. Description: res.Description,
  258. CreatedTime: res.CreatedTime.UTC(),
  259. }
  260. if res.ProjectRequirementID.Valid {
  261. projectRequirementID := int(res.ProjectRequirementID.Int32)
  262. projectID := int(res.ProjectID.Int32)
  263. item.ProjectRequirementID = &projectRequirementID
  264. item.ProjectID = &projectID
  265. }
  266. if res.ScheduledDate.Valid {
  267. item.ScheduledDate = &res.ScheduledDate.Date
  268. }
  269. if res.AcquiredTime.Valid {
  270. item.AcquiredTime = &res.AcquiredTime.Time
  271. }
  272. return item
  273. }
  274. func (r *itemRepository) fillStats(ctx context.Context, items []models.Item) error {
  275. if len(items) == 0 {
  276. return nil
  277. }
  278. ids := make([]interface{}, 0, len(items))
  279. for _, item := range items {
  280. ids = append(ids, item.ID)
  281. }
  282. query := `
  283. SELECT isp.item_id, isp.required, isp.acquired, s.id, s.name, s.weight FROM item_stat_progress isp
  284. LEFT JOIN stat s ON s.id = isp.stat_id
  285. WHERE item_id IN (?` + strings.Repeat(",?", len(ids)-1) + `);
  286. `
  287. rows, err := r.db.QueryContext(ctx, query, ids...)
  288. if err != nil {
  289. if err == sql.ErrNoRows {
  290. return nil
  291. }
  292. return err
  293. }
  294. for rows.Next() {
  295. var itemID, required, acquired, statID int
  296. var statName string
  297. var statWeight float64
  298. err = rows.Scan(&itemID, &required, &acquired, &statID, &statName, &statWeight)
  299. if err != nil {
  300. return err
  301. }
  302. for i := range items {
  303. if items[i].ID == itemID {
  304. items[i].Stats = append(items[i].Stats, models.StatProgressEntry{
  305. StatEntry: models.StatEntry{
  306. ID: statID,
  307. Name: statName,
  308. Weight: statWeight,
  309. },
  310. Acquired: acquired,
  311. Required: required,
  312. })
  313. }
  314. }
  315. }
  316. return nil
  317. }