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") }) }