GraphQL API and utilities for the rpdata project
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.

266 lines
5.8 KiB

  1. package postgres
  2. import (
  3. "context"
  4. "database/sql"
  5. "errors"
  6. "git.aiterp.net/rpdata/api/database/postgres/psqlcore"
  7. "git.aiterp.net/rpdata/api/internal/generate"
  8. "git.aiterp.net/rpdata/api/models"
  9. "strings"
  10. )
  11. type postRepository struct {
  12. insertWithIDs bool
  13. db *sql.DB
  14. }
  15. func (r *postRepository) Find(ctx context.Context, id string) (*models.Post, error) {
  16. post, err := psqlcore.New(r.db).SelectPost(ctx, id)
  17. if err != nil {
  18. return nil, err
  19. }
  20. return r.post(post), nil
  21. }
  22. func (r *postRepository) List(ctx context.Context, filter models.PostFilter) ([]*models.Post, error) {
  23. q := psqlcore.New(r.db)
  24. params := psqlcore.SelectPostsParams{LimitSize: 0}
  25. if filter.LogID != nil {
  26. params.FilterLogShortID = true
  27. if !strings.HasPrefix(*filter.LogID, "L") {
  28. log, err := q.SelectLog(ctx, *filter.LogID)
  29. if err != nil {
  30. return nil, err
  31. }
  32. params.LogShortID = log.ShortID
  33. } else {
  34. params.LogShortID = *filter.LogID
  35. }
  36. }
  37. if filter.Kinds != nil {
  38. params.FilterKinds = true
  39. params.Kinds = filter.Kinds
  40. }
  41. if filter.Search != nil {
  42. params.FilterSearch = true
  43. params.Search = *filter.Search
  44. }
  45. if filter.Limit > 0 {
  46. params.LimitSize = int32(filter.Limit)
  47. }
  48. posts, err := q.SelectPosts(ctx, params)
  49. if err != nil {
  50. return nil, err
  51. }
  52. return r.posts(posts), nil
  53. }
  54. func (r *postRepository) Insert(ctx context.Context, post models.Post) (*models.Post, error) {
  55. if !r.insertWithIDs || len(post.ID) < 8 {
  56. post.ID = generate.PostID()
  57. }
  58. position, err := psqlcore.New(r.db).InsertPost(ctx, psqlcore.InsertPostParams{
  59. ID: post.ID,
  60. LogShortID: post.LogID,
  61. Time: post.Time.UTC(),
  62. Kind: post.Kind,
  63. Nick: post.Nick,
  64. Text: post.Text,
  65. })
  66. if err != nil {
  67. return nil, err
  68. }
  69. post.Position = int(position)
  70. return &post, nil
  71. }
  72. func (r *postRepository) InsertMany(ctx context.Context, posts ...*models.Post) ([]*models.Post, error) {
  73. allowedLogID := ""
  74. for _, post := range posts {
  75. if allowedLogID == "" {
  76. allowedLogID = post.LogID
  77. } else if allowedLogID != post.LogID {
  78. return nil, errors.New("cannot insert multiple posts with different log IDs")
  79. }
  80. }
  81. params := psqlcore.InsertPostsParams{
  82. LogShortID: posts[0].LogID,
  83. }
  84. for _, post := range posts {
  85. if !r.insertWithIDs || len(post.ID) < 8 {
  86. post.ID = generate.PostID()
  87. }
  88. params.Ids = append(params.Ids, post.ID)
  89. params.Kinds = append(params.Kinds, post.Kind)
  90. params.Nicks = append(params.Nicks, post.Nick)
  91. params.Offsets = append(params.Offsets, int32(len(params.Offsets)+1))
  92. params.Times = append(params.Times, post.Time.UTC())
  93. params.Texts = append(params.Texts, post.Text)
  94. }
  95. offset, err := psqlcore.New(r.db).InsertPosts(ctx, params)
  96. if err != nil {
  97. return nil, err
  98. }
  99. for i, post := range posts {
  100. post.Position = int(offset) + i
  101. }
  102. return posts, nil
  103. }
  104. func (r *postRepository) Update(ctx context.Context, post models.Post, update models.PostUpdate) (*models.Post, error) {
  105. post.ApplyUpdate(update)
  106. err := psqlcore.New(r.db).UpdatePost(ctx, psqlcore.UpdatePostParams{
  107. Time: post.Time,
  108. Kind: post.Kind,
  109. Nick: post.Nick,
  110. Text: post.Text,
  111. ID: post.ID,
  112. })
  113. if err != nil {
  114. return nil, err
  115. }
  116. return &post, nil
  117. }
  118. func (r *postRepository) Move(ctx context.Context, post models.Post, position int) ([]*models.Post, error) {
  119. if position == post.Position {
  120. return r.List(ctx, models.PostFilter{LogID: &post.LogID})
  121. }
  122. tx, err := r.db.BeginTx(ctx, nil)
  123. if err != nil {
  124. return nil, err
  125. }
  126. defer func() { _ = tx.Rollback() }()
  127. _, err = tx.Exec("LOCK TABLE log_post IN SHARE UPDATE EXCLUSIVE MODE")
  128. if err != nil {
  129. return nil, err
  130. }
  131. var lowest, highest int32
  132. q := psqlcore.New(tx)
  133. err = q.MovePost(ctx, psqlcore.MovePostParams{ID: post.ID, Position: -1})
  134. if err != nil {
  135. return nil, err
  136. }
  137. if position > post.Position {
  138. lowest = int32(post.Position)
  139. highest = int32(position)
  140. err := q.ShiftPostsBetween(ctx, psqlcore.ShiftPostsBetweenParams{
  141. ShiftOffset: -1,
  142. LogShortID: post.LogID,
  143. FromPosition: lowest + 1,
  144. ToPosition: highest,
  145. })
  146. if err != nil {
  147. return nil, err
  148. }
  149. } else {
  150. lowest = int32(position)
  151. highest = int32(post.Position)
  152. err := q.ShiftPostsBetween(ctx, psqlcore.ShiftPostsBetweenParams{
  153. ShiftOffset: 1,
  154. LogShortID: post.LogID,
  155. FromPosition: lowest,
  156. ToPosition: highest - 1,
  157. })
  158. if err != nil {
  159. return nil, err
  160. }
  161. }
  162. err = q.MovePost(ctx, psqlcore.MovePostParams{ID: post.ID, Position: int32(position)})
  163. if err != nil {
  164. return nil, err
  165. }
  166. posts, err := q.SelectPostsByPositionRange(ctx, psqlcore.SelectPostsByPositionRangeParams{
  167. LogShortID: post.LogID,
  168. FromPosition: lowest,
  169. ToPosition: highest,
  170. })
  171. if err != nil {
  172. return nil, err
  173. }
  174. err = tx.Commit()
  175. if err != nil {
  176. return nil, err
  177. }
  178. return r.posts(posts), nil
  179. }
  180. func (r *postRepository) Delete(ctx context.Context, post models.Post) error {
  181. tx, err := r.db.BeginTx(ctx, nil)
  182. if err != nil {
  183. return err
  184. }
  185. defer func() { _ = tx.Rollback() }()
  186. q := psqlcore.New(tx)
  187. _, err = tx.Exec("LOCK TABLE log_post IN SHARE UPDATE EXCLUSIVE MODE")
  188. if err != nil {
  189. return err
  190. }
  191. err = q.DeletePost(ctx, post.ID)
  192. if err != nil {
  193. return err
  194. }
  195. err = q.ShiftPostsAfter(ctx, psqlcore.ShiftPostsAfterParams{
  196. ShiftOffset: -1,
  197. LogShortID: post.LogID,
  198. FromPosition: int32(post.Position + 1),
  199. })
  200. if err != nil {
  201. return err
  202. }
  203. return tx.Commit()
  204. }
  205. func (r *postRepository) post(post psqlcore.LogPost) *models.Post {
  206. return &models.Post{
  207. ID: post.ID,
  208. LogID: post.LogShortID,
  209. Time: post.Time,
  210. Kind: post.Kind,
  211. Nick: post.Nick,
  212. Text: post.Text,
  213. Position: int(post.Position),
  214. }
  215. }
  216. func (r *postRepository) posts(posts []psqlcore.LogPost) []*models.Post {
  217. results := make([]*models.Post, 0, len(posts))
  218. for _, post := range posts {
  219. results = append(results, r.post(post))
  220. }
  221. return results
  222. }