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.

245 lines
6.2 KiB

  1. package postgres
  2. import (
  3. "context"
  4. "database/sql"
  5. "git.aiterp.net/rpdata/api/database/postgres/psqlcore"
  6. "git.aiterp.net/rpdata/api/internal/generate"
  7. "git.aiterp.net/rpdata/api/models"
  8. )
  9. type storyRepository struct {
  10. insertWithIDs bool
  11. db *sql.DB
  12. }
  13. func (r *storyRepository) Find(ctx context.Context, id string) (*models.Story, error) {
  14. q := psqlcore.New(r.db)
  15. story, err := q.SelectStory(ctx, id)
  16. if err != nil {
  17. return nil, err
  18. }
  19. tags, err := q.SelectTagsByTarget(ctx, psqlcore.SelectTagsByTargetParams{
  20. TargetKind: "Story",
  21. TargetID: story.ID,
  22. })
  23. if err != nil && err != sql.ErrNoRows {
  24. return nil, err
  25. }
  26. return r.story(story, tags), nil
  27. }
  28. func (r *storyRepository) List(ctx context.Context, filter models.StoryFilter) ([]*models.Story, error) {
  29. q := psqlcore.New(r.db)
  30. params := psqlcore.SelectStoriesParams{LimitSize: 100}
  31. if len(filter.Tags) > 0 {
  32. targets, err := q.SelectTargetsByTags(ctx, psqlcore.SelectTargetsByTagsParams{
  33. TagNames: models.EncodeTagArray(filter.Tags),
  34. TargetKind: "Story",
  35. })
  36. if err != nil && err != sql.ErrNoRows {
  37. return nil, err
  38. }
  39. if len(params.Ids) == 0 {
  40. return []*models.Story{}, nil
  41. }
  42. params.FilterID = true
  43. params.Ids = targets
  44. }
  45. if filter.Author != nil {
  46. params.FilterAuthor = true
  47. params.Author = *filter.Author
  48. }
  49. if filter.Category != nil {
  50. params.FilterCategory = true
  51. params.Category = string(*filter.Category)
  52. }
  53. if !filter.EarliestFictionalDate.IsZero() {
  54. params.FilterEarlistFictionalDate = true
  55. params.EarliestFictionalDate = filter.EarliestFictionalDate.UTC()
  56. }
  57. if !filter.LatestFictionalDate.IsZero() {
  58. params.FilterLastestFictionalDate = true
  59. params.LatestFictionalDate = filter.LatestFictionalDate.UTC()
  60. }
  61. if filter.Open != nil {
  62. params.FilterOpen = true
  63. params.Open = *filter.Open
  64. }
  65. if filter.Unlisted != nil {
  66. params.FilterUnlisted = true
  67. params.Unlisted = *filter.Unlisted
  68. }
  69. if filter.Limit <= 0 {
  70. params.LimitSize = 1000
  71. }
  72. stories, err := q.SelectStories(ctx, params)
  73. if err != nil {
  74. return nil, err
  75. }
  76. targetIDs := make([]string, len(stories))
  77. for i, story := range stories {
  78. targetIDs[i] = story.ID
  79. }
  80. tags, err := q.SelectTagsByTargets(ctx, psqlcore.SelectTagsByTargetsParams{
  81. TargetKind: "Story",
  82. TargetIds: targetIDs,
  83. })
  84. return r.stories(stories, tags), nil
  85. }
  86. func (r *storyRepository) Insert(ctx context.Context, story models.Story) (*models.Story, error) {
  87. tx, err := r.db.BeginTx(ctx, nil)
  88. if err != nil {
  89. return nil, err
  90. }
  91. defer func() { _ = tx.Rollback() }()
  92. q := psqlcore.New(tx)
  93. if !r.insertWithIDs || len(story.ID) < 8 {
  94. story.ID = generate.StoryID()
  95. }
  96. if story.UpdatedDate.Before(story.CreatedDate) {
  97. story.UpdatedDate = story.CreatedDate
  98. }
  99. err = q.InsertStory(ctx, psqlcore.InsertStoryParams{
  100. ID: story.ID,
  101. Author: story.Author,
  102. Name: story.Name,
  103. Category: string(story.Category),
  104. Open: story.Open,
  105. Listed: story.Listed,
  106. SortByFictionalDate: story.SortByFictionalDate,
  107. CreatedDate: story.CreatedDate.UTC(),
  108. FictionalDate: story.FictionalDate.UTC(),
  109. UpdatedDate: story.UpdatedDate.UTC(),
  110. })
  111. if err != nil {
  112. return nil, err
  113. }
  114. // This is inefficient, but tags are very rarely added before the story is submitted.
  115. err = q.SetTags(ctx, psqlcore.SetTagsParams{
  116. Tags: models.EncodeTagArray(story.Tags),
  117. TargetKind: "Story",
  118. TargetID: story.ID,
  119. })
  120. if err != nil {
  121. return nil, err
  122. }
  123. return &story, tx.Commit()
  124. }
  125. func (r *storyRepository) Update(ctx context.Context, story models.Story, update models.StoryUpdate) (*models.Story, error) {
  126. story.ApplyUpdate(update)
  127. err := psqlcore.New(r.db).UpdateStory(ctx, psqlcore.UpdateStoryParams{
  128. Name: story.Name,
  129. Category: string(story.Category),
  130. Author: story.Author,
  131. Open: story.Open,
  132. Listed: story.Listed,
  133. FictionalDate: story.FictionalDate.UTC(),
  134. UpdatedDate: story.UpdatedDate.UTC(),
  135. SortByFictionalDate: story.SortByFictionalDate,
  136. ID: story.ID,
  137. })
  138. if err != nil {
  139. return nil, err
  140. }
  141. return &story, nil
  142. }
  143. func (r *storyRepository) AddTag(ctx context.Context, story models.Story, tag models.Tag) error {
  144. return psqlcore.New(r.db).SetTag(ctx, psqlcore.SetTagParams{
  145. TargetKind: "Story",
  146. TargetID: story.ID,
  147. Tag: tag.String(),
  148. })
  149. }
  150. func (r *storyRepository) RemoveTag(ctx context.Context, story models.Story, tag models.Tag) error {
  151. return psqlcore.New(r.db).ClearTag(ctx, psqlcore.ClearTagParams{
  152. TargetKind: "Story",
  153. TargetID: story.ID,
  154. Tag: tag.String(),
  155. })
  156. }
  157. func (r *storyRepository) Delete(ctx context.Context, story models.Story) error {
  158. tx, err := r.db.BeginTx(ctx, nil)
  159. if err != nil {
  160. return err
  161. }
  162. defer func() { _ = tx.Rollback() }()
  163. q := psqlcore.New(tx)
  164. err = q.ClearTagsByTarget(ctx, psqlcore.ClearTagsByTargetParams{
  165. TargetKind: "Story",
  166. TargetID: story.ID,
  167. })
  168. if err != nil {
  169. return err
  170. }
  171. err = q.DeleteChaptersByStoryID(ctx, story.ID)
  172. if err != nil {
  173. return err
  174. }
  175. err = q.DeleteStory(ctx, story.ID)
  176. if err != nil {
  177. return err
  178. }
  179. return tx.Commit()
  180. }
  181. func (r *storyRepository) story(story psqlcore.Story, tags []psqlcore.CommonTag) *models.Story {
  182. storyTags := make([]models.Tag, 0, 8)
  183. for _, tag := range tags {
  184. if tag.TargetKind == "Story" && tag.TargetID == story.ID {
  185. newTag := models.Tag{}
  186. err := newTag.Decode(tag.Tag)
  187. if err != nil {
  188. continue
  189. }
  190. storyTags = append(storyTags, newTag)
  191. }
  192. }
  193. return &models.Story{
  194. ID: story.ID,
  195. Author: story.Author,
  196. Name: story.Name,
  197. Category: models.StoryCategory(story.Category),
  198. Open: story.Open,
  199. Listed: story.Listed,
  200. Tags: storyTags,
  201. CreatedDate: story.CreatedDate,
  202. FictionalDate: story.FictionalDate,
  203. UpdatedDate: story.UpdatedDate,
  204. SortByFictionalDate: story.SortByFictionalDate,
  205. }
  206. }
  207. func (r *storyRepository) stories(stories []psqlcore.Story, tags []psqlcore.CommonTag) []*models.Story {
  208. results := make([]*models.Story, 0, len(stories))
  209. for _, story := range stories {
  210. results = append(results, r.story(story, tags))
  211. }
  212. return results
  213. }