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.

224 lines
5.4 KiB

  1. package mongodb
  2. import (
  3. "context"
  4. "errors"
  5. "git.aiterp.net/rpdata/api/internal/generate"
  6. "git.aiterp.net/rpdata/api/models"
  7. "git.aiterp.net/rpdata/api/repositories"
  8. "github.com/globalsign/mgo"
  9. "github.com/globalsign/mgo/bson"
  10. "log"
  11. "strings"
  12. )
  13. type storyRepository struct {
  14. stories *mgo.Collection
  15. chapters *mgo.Collection
  16. comments *mgo.Collection
  17. restoreIDs bool
  18. }
  19. func newStoryRepository(db *mgo.Database, restoreIDs bool) (repositories.StoryRepository, error) {
  20. collection := db.C("story.stories")
  21. err := collection.EnsureIndexKey("tags")
  22. if err != nil {
  23. return nil, err
  24. }
  25. err = collection.EnsureIndexKey("author")
  26. if err != nil {
  27. return nil, err
  28. }
  29. err = collection.EnsureIndexKey("updatedDate")
  30. if err != nil {
  31. return nil, err
  32. }
  33. err = collection.EnsureIndexKey("fictionalDate")
  34. if err != nil {
  35. return nil, err
  36. }
  37. err = collection.EnsureIndexKey("listed")
  38. if err != nil {
  39. return nil, err
  40. }
  41. return &storyRepository{
  42. stories: collection,
  43. restoreIDs: restoreIDs,
  44. chapters: db.C("story.chapters"),
  45. comments: db.C("story.comments"),
  46. }, nil
  47. }
  48. func (r *storyRepository) Find(ctx context.Context, id string) (*models.Story, error) {
  49. story := new(models.Story)
  50. err := r.stories.FindId(id).One(story)
  51. if err != nil {
  52. return nil, err
  53. }
  54. return story, nil
  55. }
  56. func (r *storyRepository) List(ctx context.Context, filter models.StoryFilter) ([]*models.Story, error) {
  57. query := bson.M{}
  58. if filter.Author != nil {
  59. query["author"] = *filter.Author
  60. }
  61. if filter.Category != nil {
  62. query["category"] = *filter.Category
  63. }
  64. if filter.Open != nil {
  65. query["open"] = *filter.Open
  66. }
  67. if len(filter.Tags) > 0 {
  68. query["tags"] = bson.M{"$all": filter.Tags}
  69. }
  70. if filter.Unlisted != nil {
  71. query["listed"] = !*filter.Unlisted
  72. } else {
  73. query["listed"] = true
  74. }
  75. if !filter.EarliestFictionalDate.IsZero() && !filter.LatestFictionalDate.IsZero() {
  76. query["fictionalDate"] = bson.M{
  77. "$gte": filter.EarliestFictionalDate,
  78. "$lte": filter.LatestFictionalDate,
  79. }
  80. } else if !filter.EarliestFictionalDate.IsZero() {
  81. query["fictionalDate"] = bson.M{
  82. "$gte": filter.EarliestFictionalDate,
  83. }
  84. } else if !filter.LatestFictionalDate.IsZero() {
  85. query["fictionalDate"] = bson.M{
  86. "$lte": filter.LatestFictionalDate,
  87. }
  88. }
  89. stories := make([]*models.Story, 0, 32)
  90. err := r.stories.Find(query).Sort("-updatedDate").Limit(filter.Limit).All(&stories)
  91. if err != nil {
  92. if err == mgo.ErrNotFound {
  93. return stories, nil
  94. }
  95. return nil, err
  96. }
  97. return stories, nil
  98. }
  99. func (r *storyRepository) Insert(ctx context.Context, story models.Story) (*models.Story, error) {
  100. if !r.restoreIDs {
  101. story.ID = generate.StoryID()
  102. } else {
  103. if len(story.ID) != len(generate.StoryID()) && strings.HasPrefix(story.ID, "S") {
  104. return nil, errors.New("invalid story id")
  105. }
  106. }
  107. err := r.stories.Insert(story)
  108. if err != nil {
  109. return nil, err
  110. }
  111. return &story, nil
  112. }
  113. func (r *storyRepository) Update(ctx context.Context, story models.Story, update models.StoryUpdate) (*models.Story, error) {
  114. updateBson := bson.M{}
  115. if update.Name != nil {
  116. updateBson["name"] = *update.Name
  117. story.Name = *update.Name
  118. }
  119. if update.Open != nil {
  120. updateBson["open"] = *update.Open
  121. story.Open = *update.Open
  122. }
  123. if update.Category != nil {
  124. updateBson["category"] = *update.Category
  125. story.Category = *update.Category
  126. }
  127. if update.Author != nil {
  128. updateBson["author"] = *update.Author
  129. story.Author = *update.Author
  130. }
  131. if update.FictionalDate != nil {
  132. updateBson["fictionalDate"] = *update.FictionalDate
  133. story.FictionalDate = *update.FictionalDate
  134. }
  135. if update.UpdatedDate != nil {
  136. updateBson["updatedDate"] = *update.UpdatedDate
  137. story.UpdatedDate = *update.UpdatedDate
  138. }
  139. if update.Listed != nil {
  140. updateBson["listed"] = *update.Listed
  141. story.Listed = *update.Listed
  142. }
  143. if update.SortByFictionalDate != nil {
  144. updateBson["sortByFictionalDate"] = *update.SortByFictionalDate
  145. story.SortByFictionalDate = *update.SortByFictionalDate
  146. }
  147. err := r.stories.UpdateId(story.ID, bson.M{"$set": updateBson})
  148. if err != nil {
  149. return nil, err
  150. }
  151. return &story, nil
  152. }
  153. func (r *storyRepository) AddTag(ctx context.Context, story models.Story, tag models.Tag) error {
  154. ci, err := r.stories.UpdateAll(bson.M{"_id": story.ID}, bson.M{"$addToSet": bson.M{"tags": tag}})
  155. if err != nil {
  156. return err
  157. }
  158. if ci.Updated == 0 {
  159. return repositories.ErrTagExists
  160. }
  161. return nil
  162. }
  163. func (r *storyRepository) RemoveTag(ctx context.Context, story models.Story, tag models.Tag) error {
  164. ci, err := r.stories.UpdateAll(bson.M{"_id": story.ID}, bson.M{"$pull": bson.M{"tags": tag}})
  165. if err != nil {
  166. return err
  167. }
  168. if ci.Updated == 0 {
  169. return repositories.ErrTagDoesNotExist
  170. }
  171. return nil
  172. }
  173. func (r *storyRepository) Delete(ctx context.Context, story models.Story) error {
  174. err := r.stories.RemoveId(story.ID)
  175. if err != nil {
  176. return err
  177. }
  178. chapterIds := make([]string, 0, 8)
  179. err = r.chapters.Find(bson.M{"storyId": story.ID}).Distinct("_id", &chapterIds)
  180. if err != nil {
  181. log.Println("Failed to find chapterIds:", err)
  182. return nil
  183. }
  184. if len(chapterIds) > 0 {
  185. c1, err := r.chapters.RemoveAll(bson.M{"storyId": story.ID})
  186. if err != nil {
  187. log.Println("Failed to remove chapters:", err)
  188. return nil
  189. }
  190. c2, err := r.comments.RemoveAll(bson.M{"chapterId": bson.M{"$in": chapterIds}})
  191. if err != nil {
  192. log.Println("Failed to remove comments:", err)
  193. return nil
  194. }
  195. log.Printf("Removed story %s (%d chapters, %d comments)", story.ID, c1.Removed, c2.Removed)
  196. }
  197. return nil
  198. }