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.

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