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.

262 lines
6.1 KiB

  1. package mongodb
  2. import (
  3. "context"
  4. "errors"
  5. "github.com/globalsign/mgo"
  6. "github.com/globalsign/mgo/bson"
  7. "sort"
  8. "strconv"
  9. "git.aiterp.net/rpdata/api/models"
  10. "git.aiterp.net/rpdata/api/repositories"
  11. )
  12. type characterRepository struct {
  13. characters *mgo.Collection
  14. cidCounter *counter
  15. restoreIDs bool
  16. }
  17. func newCharacterRepository(db *mgo.Database, restoreIDs bool) (repositories.CharacterRepository, error) {
  18. collection := db.C("common.characters")
  19. err := collection.EnsureIndexKey("name")
  20. if err != nil {
  21. return nil, err
  22. }
  23. err = collection.EnsureIndexKey("shortName")
  24. if err != nil {
  25. return nil, err
  26. }
  27. err = collection.EnsureIndexKey("author")
  28. if err != nil {
  29. return nil, err
  30. }
  31. err = collection.EnsureIndex(mgo.Index{
  32. Key: []string{"nicks"},
  33. Unique: true,
  34. DropDups: true,
  35. })
  36. if err != nil {
  37. return nil, err
  38. }
  39. err = collection.EnsureIndex(mgo.Index{
  40. Key: []string{"$text:description"},
  41. })
  42. if err != nil {
  43. return nil, err
  44. }
  45. return &characterRepository{
  46. restoreIDs: restoreIDs,
  47. characters: collection,
  48. cidCounter: newCounter(db, "auto_increment", "Character"),
  49. }, nil
  50. }
  51. func (r *characterRepository) Find(ctx context.Context, id string) (*models.Character, error) {
  52. character := new(models.Character)
  53. err := r.characters.FindId(id).One(character)
  54. if err != nil {
  55. return nil, err
  56. }
  57. return character, nil
  58. }
  59. func (r *characterRepository) FindNick(ctx context.Context, nick string) (*models.Character, error) {
  60. character := new(models.Character)
  61. err := r.characters.Find(bson.M{"nick": nick}).One(character)
  62. if err != nil {
  63. return nil, err
  64. }
  65. return character, nil
  66. }
  67. func (r *characterRepository) FindName(ctx context.Context, name string) (*models.Character, error) {
  68. query := bson.M{
  69. "$or": []bson.M{
  70. {"shortName": name},
  71. {"name": name},
  72. },
  73. }
  74. // Look for all characters matching query
  75. characters := make([]*models.Character, 0, 8)
  76. err := r.characters.Find(query).All(&characters)
  77. if err != nil {
  78. if err == mgo.ErrNotFound {
  79. return nil, repositories.ErrNotFound
  80. }
  81. return nil, err
  82. } else if len(characters) == 0 {
  83. return nil, repositories.ErrNotFound
  84. }
  85. // Prioritize exact match
  86. for _, character := range characters {
  87. if character.Name == name {
  88. return character, nil
  89. }
  90. }
  91. return characters[0], nil
  92. }
  93. func (r *characterRepository) List(ctx context.Context, filter models.CharacterFilter) ([]*models.Character, error) {
  94. query := bson.M{}
  95. if filter.Author != nil {
  96. query["author"] = *filter.Author
  97. }
  98. if len(filter.IDs) > 0 {
  99. query["_id"] = bson.M{"$in": filter.IDs}
  100. }
  101. if len(filter.Nicks) > 0 {
  102. query["nicks"] = bson.M{"$in": filter.Nicks}
  103. }
  104. if len(filter.Names) > 0 {
  105. query["$or"] = []bson.M{
  106. {"name": bson.M{"$in": filter.Names}},
  107. {"shortName": bson.M{"$in": filter.Names}},
  108. }
  109. }
  110. if filter.Search != nil {
  111. query["$text"] = bson.M{"$search": *filter.Search}
  112. }
  113. characters := make([]*models.Character, 0, 32)
  114. err := r.characters.Find(query).Limit(filter.Limit).All(&characters)
  115. if err != nil {
  116. if err == mgo.ErrNotFound {
  117. return characters, nil
  118. }
  119. return nil, err
  120. }
  121. sort.Slice(characters, func(i, j int) bool {
  122. ni, _ := strconv.Atoi(characters[i].ID[1:])
  123. nj, _ := strconv.Atoi(characters[j].ID[1:])
  124. return ni < nj
  125. })
  126. return characters, nil
  127. }
  128. func (r *characterRepository) Insert(ctx context.Context, character models.Character) (*models.Character, error) {
  129. if !r.restoreIDs {
  130. nextId, err := r.cidCounter.Increment(1)
  131. if err != nil {
  132. return nil, err
  133. }
  134. character.ID = "C" + strconv.Itoa(nextId)
  135. } else {
  136. n, err := strconv.Atoi(character.ID[1:])
  137. if err != nil {
  138. return nil, err
  139. }
  140. err = r.cidCounter.Bump(n)
  141. if err != nil {
  142. return nil, err
  143. }
  144. }
  145. err := r.characters.Insert(&character)
  146. if err != nil {
  147. return nil, err
  148. }
  149. return &character, nil
  150. }
  151. func (r *characterRepository) Update(ctx context.Context, character models.Character, update models.CharacterUpdate) (*models.Character, error) {
  152. updateBson := bson.M{}
  153. if update.Name != nil {
  154. updateBson["name"] = *update.Name
  155. character.Name = *update.Name
  156. }
  157. if update.ShortName != nil {
  158. updateBson["shortName"] = *update.ShortName
  159. character.ShortName = *update.ShortName
  160. }
  161. if update.Description != nil {
  162. updateBson["description"] = *update.Description
  163. character.Description = *update.Description
  164. }
  165. err := r.characters.UpdateId(character.ID, bson.M{"$set": updateBson})
  166. if err != nil {
  167. return nil, err
  168. }
  169. return &character, nil
  170. }
  171. func (r *characterRepository) AddNick(ctx context.Context, character models.Character, nick string) (*models.Character, error) {
  172. if character.HasNick(nick) {
  173. return nil, errors.New("nick already exist")
  174. }
  175. match := bson.M{
  176. "_id": character.ID,
  177. "nicks": bson.M{"$ne": nick},
  178. }
  179. err := r.characters.Update(match, bson.M{"$push": bson.M{"nicks": nick}})
  180. if err == mgo.ErrNotFound {
  181. return nil, repositories.ErrNotFound
  182. } else if err != nil {
  183. return nil, err
  184. }
  185. newNicks := make([]string, len(character.Nicks), len(character.Nicks)+1)
  186. copy(newNicks, character.Nicks)
  187. character.Nicks = append(newNicks, nick)
  188. return &character, nil
  189. }
  190. func (r *characterRepository) RemoveNick(ctx context.Context, character models.Character, nick string) (*models.Character, error) {
  191. if !character.HasNick(nick) {
  192. return nil, errors.New("nick does not exist")
  193. }
  194. match := bson.M{
  195. "_id": character.ID,
  196. "nicks": nick,
  197. }
  198. err := r.characters.Update(match, bson.M{"$pull": bson.M{"nicks": nick}})
  199. if err == mgo.ErrNotFound {
  200. return nil, repositories.ErrNotFound
  201. } else if mErr, ok := err.(*mgo.LastError); ok && mErr.Code == 11000 {
  202. return nil, errors.New("The nick belongs to another character already")
  203. } else if err != nil {
  204. return nil, err
  205. }
  206. newNicks := make([]string, len(character.Nicks), len(character.Nicks)+1)
  207. copy(newNicks, character.Nicks)
  208. for i := range newNicks {
  209. if newNicks[i] == nick {
  210. newNicks = append(newNicks[:i], newNicks[i+1:]...)
  211. break
  212. }
  213. }
  214. character.Nicks = newNicks
  215. return &character, nil
  216. }
  217. func (r *characterRepository) Delete(ctx context.Context, character models.Character) error {
  218. return r.characters.RemoveId(character.ID)
  219. }