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.

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