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.

260 lines
6.5 KiB

  1. package character
  2. import (
  3. "errors"
  4. "log"
  5. "strconv"
  6. "strings"
  7. "git.aiterp.net/rpdata/api/internal/store"
  8. "git.aiterp.net/rpdata/api/model/counter"
  9. "github.com/globalsign/mgo"
  10. "github.com/globalsign/mgo/bson"
  11. )
  12. var collection *mgo.Collection
  13. // Character is a common data model representing an RP character or NPC.
  14. type Character struct {
  15. ID string `json:"id" bson:"_id"`
  16. Nicks []string `json:"nicks" bson:"nicks"`
  17. Name string `json:"name" bson:"name"`
  18. ShortName string `json:"shortName" bson:"shortName"`
  19. Author string `json:"author" bson:"author"`
  20. Description string `json:"description" bson:"description"`
  21. }
  22. // HasNick returns true if the character has that nick
  23. func (character *Character) HasNick(nick string) bool {
  24. for i := range character.Nicks {
  25. if strings.EqualFold(character.Nicks[i], nick) {
  26. return true
  27. }
  28. }
  29. return false
  30. }
  31. // AddNick adds a nick to the character. It will return an error
  32. // if the nick already exists.
  33. func (character *Character) AddNick(nick string) error {
  34. for i := range character.Nicks {
  35. if strings.EqualFold(character.Nicks[i], nick) {
  36. return errors.New("Nick already exists")
  37. }
  38. }
  39. err := collection.UpdateId(character.ID, bson.M{"$push": bson.M{"nicks": nick}})
  40. if err != nil {
  41. return err
  42. }
  43. character.Nicks = append(character.Nicks, nick)
  44. return nil
  45. }
  46. // RemoveNick removes the nick from the character. It will raise
  47. // an error if the nick does not exist; even if that kind of is
  48. // the end goal.
  49. func (character *Character) RemoveNick(nick string) error {
  50. index := -1
  51. for i := range character.Nicks {
  52. if strings.EqualFold(character.Nicks[i], nick) {
  53. index = i
  54. break
  55. }
  56. }
  57. if index == -1 {
  58. return errors.New("Nick does not exist")
  59. }
  60. err := collection.UpdateId(character.ID, bson.M{"$pull": bson.M{"nicks": nick}})
  61. if err != nil {
  62. return err
  63. }
  64. character.Nicks = append(character.Nicks[:index], character.Nicks[index+1:]...)
  65. return nil
  66. }
  67. // Edit sets the fields of metadata. Only non-empty and different fields will be set in the
  68. // database, preventing out of order edits to two fields from conflicting
  69. func (character *Character) Edit(name, shortName, description string) error {
  70. changes := bson.M{}
  71. if len(name) > 0 && name != character.Name {
  72. changes["name"] = name
  73. }
  74. if len(shortName) > 0 && shortName != character.ShortName {
  75. changes["shortName"] = shortName
  76. }
  77. if len(description) > 0 && description != character.Description {
  78. changes["description"] = description
  79. }
  80. err := collection.UpdateId(character.ID, changes)
  81. if err != nil {
  82. return err
  83. }
  84. if changes["name"] != nil {
  85. character.Name = name
  86. }
  87. if changes["shortName"] != nil {
  88. character.ShortName = shortName
  89. }
  90. if changes["description"] != nil {
  91. character.Description = description
  92. }
  93. return nil
  94. }
  95. // Remove removes the character from the database. The reason this is an instance method
  96. // is that it should only be done after an authorization check.
  97. func (character *Character) Remove() error {
  98. return collection.RemoveId(character.ID)
  99. }
  100. // FindID finds Character by ID
  101. func FindID(id string) (Character, error) {
  102. return find(bson.M{"_id": id})
  103. }
  104. // FindNick finds Character by nick
  105. func FindNick(nick string) (Character, error) {
  106. return find(bson.M{"nicks": nick})
  107. }
  108. // FindName finds Character by either full name or
  109. // short name.
  110. func FindName(name string) (Character, error) {
  111. return find(bson.M{"$or": []bson.M{bson.M{"name": name}, bson.M{"shortName": name}}})
  112. }
  113. // List lists all characters
  114. func List() ([]Character, error) {
  115. return list(bson.M{})
  116. }
  117. // ListAuthor lists all characters by author
  118. func ListAuthor(author string) ([]Character, error) {
  119. return list(bson.M{"author": author})
  120. }
  121. // ListNicks lists all characters with either of these nicks. This was made with
  122. // the logbot in mind, to batch an order for characters.
  123. func ListNicks(nicks ...string) ([]Character, error) {
  124. return list(bson.M{"nicks": bson.M{"$in": nicks}})
  125. }
  126. // ListIDs lists all characters with either of these IDs.
  127. func ListIDs(ids ...string) ([]Character, error) {
  128. return list(bson.M{"_id": bson.M{"$in": ids}})
  129. }
  130. // ListFilter lists all logs matching the filters.
  131. func ListFilter(ids []string, nicks []string, names []string, author *string, search *string) ([]Character, error) {
  132. query := bson.M{}
  133. if len(ids) > 0 {
  134. query["_id"] = bson.M{"$in": ids}
  135. }
  136. if len(nicks) > 0 {
  137. query["nicks"] = bson.M{"$in": nicks}
  138. }
  139. if len(names) > 0 {
  140. query["name"] = bson.M{"$in": names}
  141. }
  142. if author != nil {
  143. query["author"] = *author
  144. }
  145. if search != nil {
  146. query["$text"] = bson.M{"$search": *search}
  147. }
  148. return list(query)
  149. }
  150. // New creates a Character and pushes it to the database. It does some validation
  151. // on nick, name, shortName and author. Leave the shortname blank to have it be the
  152. // first name.
  153. func New(nick, name, shortName, author, description string) (Character, error) {
  154. if len(nick) < 1 || len(name) < 1 || len(author) < 1 {
  155. return Character{}, errors.New("Nick, name, or author name too short or empty")
  156. }
  157. if shortName == "" {
  158. shortName = strings.SplitN(name, " ", 2)[0]
  159. }
  160. char, err := FindNick(nick)
  161. if err == nil && char.ID != "" {
  162. return Character{}, errors.New("Nick is occupied")
  163. }
  164. nextID, err := counter.Next("auto_increment", "Character")
  165. if err != nil {
  166. return Character{}, err
  167. }
  168. character := Character{
  169. ID: "C" + strconv.Itoa(nextID),
  170. Nicks: []string{nick},
  171. Name: name,
  172. ShortName: shortName,
  173. Author: author,
  174. Description: description,
  175. }
  176. err = collection.Insert(character)
  177. if err != nil {
  178. return Character{}, err
  179. }
  180. return character, nil
  181. }
  182. func find(query interface{}) (Character, error) {
  183. character := Character{}
  184. err := collection.Find(query).One(&character)
  185. if err != nil {
  186. return Character{}, err
  187. }
  188. return character, nil
  189. }
  190. func list(query interface{}) ([]Character, error) {
  191. characters := make([]Character, 0, 64)
  192. err := collection.Find(query).All(&characters)
  193. if err != nil {
  194. return nil, err
  195. }
  196. return characters, nil
  197. }
  198. func init() {
  199. store.HandleInit(func(db *mgo.Database) {
  200. collection = db.C("common.characters")
  201. collection.EnsureIndexKey("name")
  202. collection.EnsureIndexKey("shortName")
  203. collection.EnsureIndexKey("author")
  204. err := collection.EnsureIndex(mgo.Index{
  205. Key: []string{"nicks"},
  206. Unique: true,
  207. DropDups: true,
  208. })
  209. if err != nil {
  210. log.Fatalln("init common.characters:", err)
  211. }
  212. err = collection.EnsureIndex(mgo.Index{
  213. Key: []string{"$text:description"},
  214. })
  215. if err != nil {
  216. log.Fatalln("init common.characters:", err)
  217. }
  218. })
  219. }