|
|
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"` }
// Filter is used to filter the list of characters
type Filter struct { IDs []string `json:"ids"` Nicks []string `json:"nicks"` Names []string `json:"names"` Author *string `json:"author"` Search *string `json:"search"` Logged *bool `json:"logged"` }
// Nick gets the character's nick.
func (character *Character) Nick() *string { if len(character.Nicks[0]) == 0 { return nil }
return &character.Nicks[0] }
// 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(filter *Filter) ([]Character, error) { query := bson.M{}
if filter != nil { if len(filter.IDs) > 1 { query["id"] = bson.M{"$in": filter.IDs} } else if len(filter.IDs) == 1 { query["id"] = filter.IDs[0] }
if len(filter.Nicks) > 1 { query["nicks"] = bson.M{"$in": filter.Nicks} } else if len(filter.Nicks) == 1 { query["nicks"] = filter.Nicks[0] }
if len(filter.Names) > 1 { query["$or"] = bson.M{ "name": bson.M{"$in": filter.Names}, "shortName": bson.M{"$in": filter.Names}, } } else if len(filter.Names) == 1 { query["$or"] = bson.M{ "name": filter.Names[0], "shortName": filter.Names[0], } }
if filter.Logged != nil { query["logged"] = *filter.Logged }
if filter.Author != nil { query["author"] = *filter.Author }
if filter.Search != nil { query["$text"] = bson.M{"$search": *filter.Search} } }
return list(query) }
// 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") }) }
|