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.

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