Loggest thine 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.

620 lines
15 KiB

2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
  1. package mysql
  2. import (
  3. "context"
  4. "database/sql"
  5. "git.aiterp.net/stufflog3/stufflog3/entities"
  6. "git.aiterp.net/stufflog3/stufflog3/internal/genutils"
  7. "git.aiterp.net/stufflog3/stufflog3/models"
  8. "git.aiterp.net/stufflog3/stufflog3/ports/mysql/mysqlcore"
  9. "github.com/Masterminds/squirrel"
  10. )
  11. type projectRepository struct {
  12. db *sql.DB
  13. q *mysqlcore.Queries
  14. }
  15. func (r *projectRepository) Find(ctx context.Context, scopeID, projectID int) (*entities.Project, error) {
  16. row, err := r.q.GetProject(ctx, mysqlcore.GetProjectParams{ID: projectID, ScopeID: scopeID})
  17. if err != nil {
  18. if err == sql.ErrNoRows {
  19. return nil, models.NotFoundError("Project")
  20. }
  21. return nil, err
  22. }
  23. tags, err := r.q.ListTagsByObject(ctx, mysqlcore.ListTagsByObjectParams{
  24. ObjectKind: tagObjectKindProject,
  25. ObjectID: row.ID,
  26. })
  27. return &entities.Project{
  28. ID: row.ID,
  29. ScopeID: row.ScopeID,
  30. OwnerID: row.OwnerID,
  31. CreatedTime: row.CreatedTime,
  32. Name: row.Name,
  33. Description: row.Description,
  34. Status: models.Status(row.Status),
  35. Tags: tags,
  36. }, nil
  37. }
  38. func (r *projectRepository) FetchProjects(ctx context.Context, scopeID int, ids ...int) ([]entities.Project, error) {
  39. if len(ids) == 0 {
  40. return []entities.Project{}, nil
  41. } else if len(ids) == 1 && scopeID != -1 {
  42. project, err := r.Find(ctx, scopeID, ids[0])
  43. if err != nil {
  44. return nil, err
  45. }
  46. return []entities.Project{*project}, nil
  47. }
  48. sq := squirrel.Select("id,scope_id,owner_id,name,status,description,created_time").
  49. From("project").
  50. Where(squirrel.Eq{"id": ids})
  51. if scopeID != -1 {
  52. sq = sq.Where(squirrel.Eq{"scope_id": scopeID})
  53. }
  54. query, args, err := sq.ToSql()
  55. if err != nil {
  56. return nil, err
  57. }
  58. rows, err := r.db.QueryContext(ctx, query, args...)
  59. if err != nil {
  60. return nil, err
  61. }
  62. projects := make([]entities.Project, 0, len(ids))
  63. for rows.Next() {
  64. project := entities.Project{}
  65. if err := rows.Scan(
  66. &project.ID,
  67. &project.ScopeID,
  68. &project.OwnerID,
  69. &project.Name,
  70. &project.Status,
  71. &project.Description,
  72. &project.CreatedTime,
  73. ); err != nil {
  74. return nil, err
  75. }
  76. project.Tags = []string{}
  77. projects = append(projects, project)
  78. }
  79. if err := rows.Close(); err != nil {
  80. return nil, err
  81. }
  82. if err := rows.Err(); err != nil {
  83. return nil, err
  84. }
  85. // Fill tags
  86. err = fetchTags(ctx, r.db, tagObjectKindProject, ids, func(id int, tag string) {
  87. for i := range projects {
  88. if projects[i].ID == id {
  89. projects[i].Tags = append(projects[i].Tags, tag)
  90. }
  91. }
  92. })
  93. if err != nil {
  94. return nil, err
  95. }
  96. return projects, nil
  97. }
  98. func (r *projectRepository) List(ctx context.Context, scopeID int) ([]entities.Project, error) {
  99. rows, err := r.q.ListProjects(ctx, scopeID)
  100. if err != nil {
  101. if err == sql.ErrNoRows {
  102. return []entities.Project{}, nil
  103. }
  104. return nil, err
  105. }
  106. res := make([]entities.Project, 0, len(rows))
  107. ids := make([]int, 0, len(rows))
  108. for _, row := range rows {
  109. res = append(res, entities.Project{
  110. ID: row.ID,
  111. ScopeID: row.ScopeID,
  112. OwnerID: row.OwnerID,
  113. CreatedTime: row.CreatedTime,
  114. Name: row.Name,
  115. Description: row.Description,
  116. Status: models.Status(row.Status),
  117. Tags: []string{},
  118. })
  119. ids = append(ids, row.ID)
  120. }
  121. // Fill tags
  122. err = fetchTags(ctx, r.db, tagObjectKindProject, ids, func(id int, tag string) {
  123. for i := range res {
  124. if id == res[i].ID {
  125. res[i].Tags = append(res[i].Tags, tag)
  126. }
  127. }
  128. })
  129. if err != nil {
  130. return nil, err
  131. }
  132. return res, nil
  133. }
  134. func (r *projectRepository) ListByTags(ctx context.Context, scopeID int, tags []string) ([]entities.Project, error) {
  135. query, args, err := squirrel.Select("object_id, tag_name").
  136. From("tag").
  137. Where(squirrel.Eq{"tag_name": tags, "object_kind": tagObjectKindProject}).
  138. ToSql()
  139. if err != nil {
  140. return nil, err
  141. }
  142. rows, err := r.db.QueryContext(ctx, query, args...)
  143. if err != nil {
  144. return nil, err
  145. }
  146. ids := make([]int, 0, 16)
  147. matches := make(map[int]int, 64)
  148. for rows.Next() {
  149. var objectID int
  150. var tagName string
  151. err := rows.Scan(&objectID, &tagName)
  152. if err != nil {
  153. return nil, err
  154. }
  155. if matches[objectID] == 0 {
  156. ids = append(ids, objectID)
  157. }
  158. matches[objectID] += 1
  159. }
  160. err = rows.Close()
  161. if err != nil {
  162. return nil, err
  163. }
  164. ids = genutils.RetainInPlace(ids, func(id int) bool {
  165. return matches[id] == len(tags)
  166. })
  167. return r.FetchProjects(ctx, scopeID, ids...)
  168. }
  169. func (r *projectRepository) Insert(ctx context.Context, project entities.Project) (*entities.Project, error) {
  170. tx, err := r.db.BeginTx(ctx, nil)
  171. if err != nil {
  172. return nil, err
  173. }
  174. defer tx.Rollback()
  175. q := r.q.WithTx(tx)
  176. res, err := q.InsertProject(ctx, mysqlcore.InsertProjectParams{
  177. ScopeID: project.ScopeID,
  178. OwnerID: project.OwnerID,
  179. Name: project.Name,
  180. Status: int(project.Status),
  181. Description: project.Description,
  182. })
  183. if err != nil {
  184. return nil, err
  185. }
  186. id, err := res.LastInsertId()
  187. if err != nil {
  188. return nil, err
  189. }
  190. project.ID = int(id)
  191. for _, tag := range project.Tags {
  192. err := q.InsertTag(ctx, mysqlcore.InsertTagParams{
  193. ObjectKind: tagObjectKindProject,
  194. ObjectID: project.ID,
  195. TagName: tag,
  196. })
  197. if err != nil {
  198. return nil, err
  199. }
  200. }
  201. err = tx.Commit()
  202. if err != nil {
  203. return nil, err
  204. }
  205. return &project, nil
  206. }
  207. func (r *projectRepository) Update(ctx context.Context, project entities.Project, update models.ProjectUpdate) error {
  208. tx, err := r.db.BeginTx(ctx, nil)
  209. if err != nil {
  210. return err
  211. }
  212. defer tx.Rollback()
  213. q := r.q.WithTx(tx)
  214. project.Update(update)
  215. err = q.UpdateProject(ctx, mysqlcore.UpdateProjectParams{
  216. OwnerID: project.OwnerID,
  217. Name: project.Name,
  218. Status: int(project.Status),
  219. Description: project.Description,
  220. ID: project.ID,
  221. ScopeID: project.ScopeID,
  222. })
  223. if err != nil {
  224. return err
  225. }
  226. for _, tag := range update.RemoveTags {
  227. err := q.DeleteTag(ctx, mysqlcore.DeleteTagParams{
  228. ObjectKind: tagObjectKindProject,
  229. ObjectID: project.ID,
  230. TagName: tag,
  231. })
  232. if err != nil {
  233. return err
  234. }
  235. }
  236. for _, tag := range update.AddTags {
  237. err = q.InsertTag(ctx, mysqlcore.InsertTagParams{
  238. ObjectKind: tagObjectKindProject,
  239. ObjectID: project.ID,
  240. TagName: tag,
  241. })
  242. if err != nil {
  243. return err
  244. }
  245. }
  246. return tx.Commit()
  247. }
  248. func (r *projectRepository) Delete(ctx context.Context, project entities.Project) error {
  249. tx, err := r.db.BeginTx(ctx, nil)
  250. if err != nil {
  251. return err
  252. }
  253. defer tx.Rollback()
  254. q := r.q.WithTx(tx)
  255. reqs, err := q.ListProjectRequirements(ctx, project.ID)
  256. if err != nil {
  257. return err
  258. }
  259. for _, req := range reqs {
  260. err = q.ClearItemProjectRequirement(ctx, sql.NullInt32{Valid: true, Int32: int32(req.ID)})
  261. if err != nil {
  262. return err
  263. }
  264. err = q.DeleteAllProjectRequirementStats(ctx, req.ID)
  265. if err != nil {
  266. return err
  267. }
  268. err = q.DeleteTagByObject(ctx, mysqlcore.DeleteTagByObjectParams{
  269. ObjectKind: tagObjectKindRequirement,
  270. ObjectID: req.ID,
  271. })
  272. if err != nil {
  273. return err
  274. }
  275. }
  276. err = q.DeleteTagByObject(ctx, mysqlcore.DeleteTagByObjectParams{
  277. ObjectKind: tagObjectKindProject,
  278. ObjectID: project.ID,
  279. })
  280. if err != nil {
  281. return err
  282. }
  283. err = q.DeleteAllProjectRequirements(ctx, project.ID)
  284. if err != nil {
  285. return err
  286. }
  287. err = q.DeleteProject(ctx, mysqlcore.DeleteProjectParams{ID: project.ID, ScopeID: project.ScopeID})
  288. if err != nil {
  289. return err
  290. }
  291. return tx.Commit()
  292. }
  293. func (r *projectRepository) FetchRequirements(ctx context.Context, scopeID int, requirementIDs ...int) ([]entities.Requirement, []entities.RequirementStat, error) {
  294. if len(requirementIDs) == 0 {
  295. return []entities.Requirement{}, []entities.RequirementStat{}, nil
  296. }
  297. sq := squirrel.Select("id, scope_id, project_id, name, status, description, is_coarse, aggregate_required").
  298. From("project_requirement").
  299. Where(squirrel.Eq{"id": requirementIDs})
  300. if scopeID != -1 {
  301. sq = sq.Where(squirrel.Eq{"scope_id": scopeID})
  302. }
  303. query, args, err := sq.ToSql()
  304. if err != nil {
  305. return nil, nil, err
  306. }
  307. rows, err := r.db.QueryContext(ctx, query, args...)
  308. if err != nil {
  309. return nil, nil, err
  310. }
  311. ids := make([]int, 0, 16)
  312. requirements := make([]entities.Requirement, 0, len(requirementIDs))
  313. for rows.Next() {
  314. requirement := entities.Requirement{}
  315. if err := rows.Scan(
  316. &requirement.ID,
  317. &requirement.ScopeID,
  318. &requirement.ProjectID,
  319. &requirement.Name,
  320. &requirement.Status,
  321. &requirement.Description,
  322. &requirement.IsCoarse,
  323. &requirement.AggregateRequired,
  324. ); err != nil {
  325. return nil, nil, err
  326. }
  327. requirement.Tags = []string{}
  328. requirements = append(requirements, requirement)
  329. ids = append(ids, requirement.ID)
  330. }
  331. // Fill tags
  332. err = fetchTags(ctx, r.db, tagObjectKindRequirement, ids, func(id int, tag string) {
  333. for i := range requirements {
  334. if id == requirements[i].ID {
  335. requirements[i].Tags = append(requirements[i].Tags, tag)
  336. break
  337. }
  338. }
  339. })
  340. if err != nil {
  341. return nil, nil, err
  342. }
  343. query, args, err = squirrel.Select("project_requirement_id, stat_id, required").
  344. From("project_requirement_stat").
  345. Where(squirrel.Eq{"project_requirement_id": requirementIDs}).
  346. ToSql()
  347. if err != nil {
  348. return nil, nil, err
  349. }
  350. rows, err = r.db.QueryContext(ctx, query, args...)
  351. if err != nil {
  352. return nil, nil, err
  353. }
  354. stats := make([]entities.RequirementStat, 0, len(requirementIDs))
  355. for rows.Next() {
  356. stat := entities.RequirementStat{}
  357. if err := rows.Scan(
  358. &stat.RequirementID,
  359. &stat.StatID,
  360. &stat.Required,
  361. ); err != nil {
  362. return nil, nil, err
  363. }
  364. stats = append(stats, stat)
  365. }
  366. return requirements, stats, nil
  367. }
  368. func (r *projectRepository) ListRequirements(ctx context.Context, projectID int) ([]entities.Requirement, []entities.RequirementStat, error) {
  369. reqRows, err := r.q.ListProjectRequirements(ctx, projectID)
  370. if err != nil && err != sql.ErrNoRows {
  371. return nil, nil, err
  372. }
  373. statsRows, err := r.q.ListProjectRequirementsStats(ctx, projectID)
  374. if err != nil && err != sql.ErrNoRows {
  375. return nil, nil, err
  376. }
  377. requirements := make([]entities.Requirement, 0, len(reqRows))
  378. ids := make([]int, 0, len(reqRows))
  379. for _, row := range reqRows {
  380. requirements = append(requirements, entities.Requirement{
  381. ID: row.ID,
  382. ScopeID: row.ScopeID,
  383. ProjectID: row.ProjectID,
  384. Name: row.Name,
  385. Description: row.Description,
  386. IsCoarse: row.IsCoarse,
  387. AggregateRequired: row.AggregateRequired,
  388. Status: models.Status(row.Status),
  389. Tags: []string{},
  390. })
  391. ids = append(ids, row.ID)
  392. }
  393. // Fill tags
  394. err = fetchTags(ctx, r.db, tagObjectKindRequirement, ids, func(id int, tag string) {
  395. for i := range requirements {
  396. if id == requirements[i].ID {
  397. requirements[i].Tags = append(requirements[i].Tags, tag)
  398. break
  399. }
  400. }
  401. })
  402. if err != nil {
  403. return nil, nil, err
  404. }
  405. stats := make([]entities.RequirementStat, 0, len(statsRows))
  406. for _, row := range statsRows {
  407. stats = append(stats, entities.RequirementStat{
  408. RequirementID: row.ProjectRequirementID,
  409. StatID: row.StatID,
  410. Required: row.Required,
  411. })
  412. }
  413. return requirements, stats, nil
  414. }
  415. func (r *projectRepository) CreateRequirement(ctx context.Context, requirement entities.Requirement) (*entities.Requirement, error) {
  416. tx, err := r.db.BeginTx(ctx, nil)
  417. if err != nil {
  418. return nil, err
  419. }
  420. defer tx.Rollback()
  421. q := r.q.WithTx(tx)
  422. res, err := q.InsertProjectRequirement(ctx, mysqlcore.InsertProjectRequirementParams{
  423. ScopeID: requirement.ScopeID,
  424. ProjectID: requirement.ProjectID,
  425. Name: requirement.Name,
  426. Status: int(requirement.Status),
  427. Description: requirement.Description,
  428. IsCoarse: requirement.IsCoarse,
  429. AggregateRequired: requirement.AggregateRequired,
  430. })
  431. if err != nil {
  432. return nil, err
  433. }
  434. id, err := res.LastInsertId()
  435. if err != nil {
  436. return nil, err
  437. }
  438. for _, tag := range requirement.Tags {
  439. err := q.InsertTag(ctx, mysqlcore.InsertTagParams{
  440. ObjectKind: tagObjectKindRequirement,
  441. ObjectID: int(id),
  442. TagName: tag,
  443. })
  444. if err != nil {
  445. return nil, err
  446. }
  447. }
  448. err = tx.Commit()
  449. if err != nil {
  450. return nil, err
  451. }
  452. requirement.ID = int(id)
  453. return &requirement, nil
  454. }
  455. func (r *projectRepository) UpdateRequirement(ctx context.Context, requirement entities.Requirement, update models.RequirementUpdate) error {
  456. tx, err := r.db.BeginTx(ctx, nil)
  457. if err != nil {
  458. return err
  459. }
  460. defer tx.Rollback()
  461. q := r.q.WithTx(tx)
  462. requirement.Update(update)
  463. _ = q.UpdateProjectRequirement(ctx, mysqlcore.UpdateProjectRequirementParams{
  464. Name: requirement.Name,
  465. Status: int(requirement.Status),
  466. Description: requirement.Description,
  467. IsCoarse: requirement.IsCoarse,
  468. ID: requirement.ID,
  469. ScopeID: requirement.ScopeID,
  470. AggregateRequired: requirement.AggregateRequired,
  471. ProjectID: requirement.ProjectID,
  472. })
  473. for _, tag := range update.RemoveTags {
  474. err := q.DeleteTag(ctx, mysqlcore.DeleteTagParams{
  475. ObjectKind: tagObjectKindRequirement,
  476. ObjectID: requirement.ID,
  477. TagName: tag,
  478. })
  479. if err != nil {
  480. return err
  481. }
  482. }
  483. for _, tag := range update.AddTags {
  484. err = q.InsertTag(ctx, mysqlcore.InsertTagParams{
  485. ObjectKind: tagObjectKindRequirement,
  486. ObjectID: requirement.ID,
  487. TagName: tag,
  488. })
  489. if err != nil {
  490. return err
  491. }
  492. }
  493. return tx.Commit()
  494. }
  495. func (r *projectRepository) DeleteRequirement(ctx context.Context, requirement entities.Requirement) error {
  496. tx, err := r.db.BeginTx(ctx, nil)
  497. if err != nil {
  498. return err
  499. }
  500. defer tx.Rollback()
  501. q := r.q.WithTx(tx)
  502. err = q.ClearItemProjectRequirement(ctx, sql.NullInt32{Valid: true, Int32: int32(requirement.ID)})
  503. if err != nil {
  504. return err
  505. }
  506. err = q.DeleteAllProjectRequirementStats(ctx, requirement.ID)
  507. if err != nil {
  508. return err
  509. }
  510. err = q.DeleteProjectRequirement(ctx, mysqlcore.DeleteProjectRequirementParams{
  511. ScopeID: requirement.ScopeID,
  512. ID: requirement.ID,
  513. })
  514. if err != nil {
  515. return err
  516. }
  517. err = q.DeleteTagByObject(ctx, mysqlcore.DeleteTagByObjectParams{
  518. ObjectKind: tagObjectKindRequirement,
  519. ObjectID: requirement.ID,
  520. })
  521. if err != nil {
  522. return err
  523. }
  524. return tx.Commit()
  525. }
  526. func (r *projectRepository) UpsertRequirementStat(ctx context.Context, stat entities.RequirementStat) error {
  527. return r.q.ReplaceProjectRequirementStat(ctx, mysqlcore.ReplaceProjectRequirementStatParams{
  528. ProjectRequirementID: stat.RequirementID,
  529. StatID: stat.StatID,
  530. Required: stat.Required,
  531. })
  532. }
  533. func (r *projectRepository) DeleteRequirementStat(ctx context.Context, stat entities.RequirementStat) error {
  534. return r.q.DeleteProjectRequirementStat(ctx, mysqlcore.DeleteProjectRequirementStatParams{
  535. ProjectRequirementID: stat.RequirementID,
  536. StatID: stat.StatID,
  537. })
  538. }