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