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
286 lines
7.1 KiB
package character
|
|
|
|
import (
|
|
"errors"
|
|
"log"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"git.aiterp.net/rpdata/api/internal/store"
|
|
"git.aiterp.net/rpdata/api/model/counter"
|
|
"github.com/globalsign/mgo"
|
|
"github.com/globalsign/mgo/bson"
|
|
)
|
|
|
|
var collection *mgo.Collection
|
|
|
|
var logsCollection *mgo.Collection
|
|
|
|
// Character is a common data model representing an RP character or NPC.
|
|
type Character struct {
|
|
ID string `json:"id" bson:"_id"`
|
|
Nicks []string `json:"nicks" bson:"nicks"`
|
|
Name string `json:"name" bson:"name"`
|
|
ShortName string `json:"shortName" bson:"shortName"`
|
|
Author string `json:"author" bson:"author"`
|
|
Description string `json:"description" bson:"description"`
|
|
}
|
|
|
|
// HasNick returns true if the character has that nick
|
|
func (character *Character) HasNick(nick string) bool {
|
|
for i := range character.Nicks {
|
|
if strings.EqualFold(character.Nicks[i], nick) {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// AddNick adds a nick to the character. It will return an error
|
|
// if the nick already exists.
|
|
func (character *Character) AddNick(nick string) error {
|
|
for i := range character.Nicks {
|
|
if strings.EqualFold(character.Nicks[i], nick) {
|
|
return errors.New("Nick already exists")
|
|
}
|
|
}
|
|
|
|
err := collection.UpdateId(character.ID, bson.M{"$push": bson.M{"nicks": nick}})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
character.Nicks = append(character.Nicks, nick)
|
|
|
|
return nil
|
|
}
|
|
|
|
// RemoveNick removes the nick from the character. It will raise
|
|
// an error if the nick does not exist; even if that kind of is
|
|
// the end goal.
|
|
func (character *Character) RemoveNick(nick string) error {
|
|
index := -1
|
|
for i := range character.Nicks {
|
|
if strings.EqualFold(character.Nicks[i], nick) {
|
|
index = i
|
|
break
|
|
}
|
|
}
|
|
if index == -1 {
|
|
return errors.New("Nick does not exist")
|
|
}
|
|
|
|
err := collection.UpdateId(character.ID, bson.M{"$pull": bson.M{"nicks": nick}})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
character.Nicks = append(character.Nicks[:index], character.Nicks[index+1:]...)
|
|
|
|
return nil
|
|
}
|
|
|
|
// Edit sets the fields of metadata. Only non-empty and different fields will be set in the
|
|
// database, preventing out of order edits to two fields from conflicting
|
|
func (character *Character) Edit(name, shortName, description string) error {
|
|
changes := bson.M{}
|
|
if len(name) > 0 && name != character.Name {
|
|
changes["name"] = name
|
|
}
|
|
if len(shortName) > 0 && shortName != character.ShortName {
|
|
changes["shortName"] = shortName
|
|
}
|
|
if len(description) > 0 && description != character.Description {
|
|
changes["description"] = description
|
|
}
|
|
|
|
err := collection.UpdateId(character.ID, changes)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if changes["name"] != nil {
|
|
character.Name = name
|
|
}
|
|
if changes["shortName"] != nil {
|
|
character.ShortName = shortName
|
|
}
|
|
if changes["description"] != nil {
|
|
character.Description = description
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Remove removes the character from the database. The reason this is an instance method
|
|
// is that it should only be done after an authorization check.
|
|
func (character *Character) Remove() error {
|
|
return collection.RemoveId(character.ID)
|
|
}
|
|
|
|
// FindID finds Character by ID
|
|
func FindID(id string) (Character, error) {
|
|
return find(bson.M{"_id": id})
|
|
}
|
|
|
|
// FindNick finds Character by nick
|
|
func FindNick(nick string) (Character, error) {
|
|
return find(bson.M{"nicks": nick})
|
|
}
|
|
|
|
// FindName finds Character by either full name or
|
|
// short name.
|
|
func FindName(name string) (Character, error) {
|
|
return find(bson.M{"$or": []bson.M{bson.M{"name": name}, bson.M{"shortName": name}}})
|
|
}
|
|
|
|
// List lists all characters
|
|
func List() ([]Character, error) {
|
|
return list(bson.M{})
|
|
}
|
|
|
|
// ListAuthor lists all characters by author
|
|
func ListAuthor(author string) ([]Character, error) {
|
|
return list(bson.M{"author": author})
|
|
}
|
|
|
|
// ListNicks lists all characters with either of these nicks. This was made with
|
|
// the logbot in mind, to batch an order for characters.
|
|
func ListNicks(nicks ...string) ([]Character, error) {
|
|
return list(bson.M{"nicks": bson.M{"$in": nicks}})
|
|
}
|
|
|
|
// ListIDs lists all characters with either of these IDs.
|
|
func ListIDs(ids ...string) ([]Character, error) {
|
|
return list(bson.M{"_id": bson.M{"$in": ids}})
|
|
}
|
|
|
|
// ListFilter lists all logs matching the filters.
|
|
func ListFilter(ids []string, nicks []string, names []string, author *string, search *string, logged *bool) ([]Character, error) {
|
|
query := bson.M{}
|
|
|
|
if logged != nil {
|
|
loggedIDs := make([]string, 0, 64)
|
|
err := logsCollection.Find(bson.M{"characterIds": bson.M{"$ne": nil}}).Distinct("characterIds", &loggedIDs)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if len(ids) > 0 {
|
|
newIds := make([]string, 0, len(ids))
|
|
for _, id := range ids {
|
|
for _, loggedID := range loggedIDs {
|
|
if id == loggedID {
|
|
newIds = append(newIds, id)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
ids = newIds
|
|
} else {
|
|
ids = loggedIDs
|
|
}
|
|
}
|
|
if len(ids) > 0 {
|
|
query["_id"] = bson.M{"$in": ids}
|
|
}
|
|
if len(nicks) > 0 {
|
|
query["nicks"] = bson.M{"$in": nicks}
|
|
}
|
|
if len(names) > 0 {
|
|
query["name"] = bson.M{"$in": names}
|
|
}
|
|
if author != nil {
|
|
query["author"] = *author
|
|
}
|
|
if search != nil {
|
|
query["$text"] = bson.M{"$search": *search}
|
|
}
|
|
|
|
return list(query)
|
|
}
|
|
|
|
// New creates a Character and pushes it to the database. It does some validation
|
|
// on nick, name, shortName and author. Leave the shortname blank to have it be the
|
|
// first name.
|
|
func New(nick, name, shortName, author, description string) (Character, error) {
|
|
if len(nick) < 1 || len(name) < 1 || len(author) < 1 {
|
|
return Character{}, errors.New("Nick, name, or author name too short or empty")
|
|
}
|
|
if shortName == "" {
|
|
shortName = strings.SplitN(name, " ", 2)[0]
|
|
}
|
|
|
|
char, err := FindNick(nick)
|
|
if err == nil && char.ID != "" {
|
|
return Character{}, errors.New("Nick is occupied")
|
|
}
|
|
|
|
nextID, err := counter.Next("auto_increment", "Character")
|
|
if err != nil {
|
|
return Character{}, err
|
|
}
|
|
|
|
character := Character{
|
|
ID: "C" + strconv.Itoa(nextID),
|
|
Nicks: []string{nick},
|
|
Name: name,
|
|
ShortName: shortName,
|
|
Author: author,
|
|
Description: description,
|
|
}
|
|
|
|
err = collection.Insert(character)
|
|
if err != nil {
|
|
return Character{}, err
|
|
}
|
|
|
|
return character, nil
|
|
}
|
|
|
|
func find(query interface{}) (Character, error) {
|
|
character := Character{}
|
|
err := collection.Find(query).One(&character)
|
|
if err != nil {
|
|
return Character{}, err
|
|
}
|
|
|
|
return character, nil
|
|
}
|
|
|
|
func list(query interface{}) ([]Character, error) {
|
|
characters := make([]Character, 0, 64)
|
|
err := collection.Find(query).All(&characters)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return characters, nil
|
|
}
|
|
|
|
func init() {
|
|
store.HandleInit(func(db *mgo.Database) {
|
|
collection = db.C("common.characters")
|
|
|
|
collection.EnsureIndexKey("name")
|
|
collection.EnsureIndexKey("shortName")
|
|
collection.EnsureIndexKey("author")
|
|
err := collection.EnsureIndex(mgo.Index{
|
|
Key: []string{"nicks"},
|
|
Unique: true,
|
|
DropDups: true,
|
|
})
|
|
if err != nil {
|
|
log.Fatalln("init common.characters:", err)
|
|
}
|
|
err = collection.EnsureIndex(mgo.Index{
|
|
Key: []string{"$text:description"},
|
|
})
|
|
if err != nil {
|
|
log.Fatalln("init common.characters:", err)
|
|
}
|
|
|
|
logsCollection = db.C("logbot3.logs")
|
|
})
|
|
}
|