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.

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