package mongodb import ( "context" "errors" "fmt" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" "hacklab.nilfm.cc/felt/models" "time" ) const errNoCollection string = "collection not found: felt.%s" const errNoDocument string = "document with name/id '%s' doesn't exist in collection: %s" const errNotAString string = "document property is not a string: %s.%s" const errNotAnArray string = "document property is not an array: %s.%s" const ErrNotFound string = "this token doesn't exist at this table; forget about it" type DbAdapter interface { Init(mongoUri string) error CreateTable(table models.TableKey) error DestroyTable(table models.TableKey) error CheckTable(table models.TableKey) bool GetProtocols() ([]string, error) InsertDiceRoll(table models.TableKey, diceRoll models.DiceRoll) error GetDiceRolls(table models.TableKey) ([]models.DiceRoll, error) SetMapImageUrl(table models.TableKey, url string) error GetMapImageUrl(table models.TableKey) (string, error) SetAuxMessage(table models.TableKey, message string) error GetAuxMessage(table models.TableKey) (string, error) CheckToken(table models.TableKey, tokenId string) (bool, bool) CreateToken(table models.TableKey, token models.Token) (string, error) ActivateToken(table models.TableKey, tokenId string, active bool) error MoveToken(table models.TableKey, token models.Token) error DestroyToken(table models.TableKey, tokenId string) error GetTokens(table models.TableKey, activeOnly bool) ([]models.Token, error) } type DbEngine struct { client *mongo.Client db *mongo.Database } func (self *DbEngine) mkCtx(timeoutSec int) context.Context { ctx, _ := context.WithTimeout(context.Background(), 10*time.Second) return ctx } func (self *DbEngine) Init(mongoUri string) error { client, err := mongo.NewClient(options.Client().ApplyURI(mongoUri)) if err != nil { return err } self.client = client ctx := self.mkCtx(10) err = client.Connect(ctx) if err != nil { return err } db := client.Database("felt") self.db = db err = self.ensureCollections(db) return err } func (self *DbEngine) ensureCollections(db *mongo.Database) error { tables := db.Collection("tables") if tables == nil { createCmd := bson.D{ {"create", "tables"}, {"clusteredIndex", bson.D{ {"key", "name"}, {"unique", true}, {"name", "idx_tables_unique_names"}, }}, } var createResult bson.M err := db.RunCommand( self.mkCtx(10), createCmd).Decode(&createResult) if err != nil { return err } } return nil } func (self *DbEngine) CreateTable(table models.TableKey) error { tables := self.db.Collection("tables") if tables != nil { _, err := tables.InsertOne(self.mkCtx(10), bson.D{ {"name", table.Name}, {"passcode", table.Passcode}, {"mapImageUrl", ""}, {"diceRolls", bson.A{}}, {"tokens", bson.A{}}, {"availableTokens", bson.A{}}, }) return err } return errors.New(fmt.Sprintf(errNoCollection, "tables")) } func (self *DbEngine) DestroyTable(table models.TableKey) error { tables := self.db.Collection("tables") if tables != nil { _, err := tables.DeleteOne(self.mkCtx(10), bson.D{ {"name", table.Name}, {"passcode", table.Passcode}, }) return err } return errors.New(fmt.Sprintf(errNoCollection, "tables")) } func (self *DbEngine) CheckTable(table models.TableKey) bool { tables := self.db.Collection("tables") if tables != nil { res := tables.FindOne(self.mkCtx(10), bson.D{ {"name", table.Name}, {"passcode", table.Passcode}, }) return res != nil } return false } func (self *DbEngine) GetProtocols() ([]string, error) { tables := self.db.Collection("tables") if tables != nil { var results []models.Table cursor, err := tables.Find(self.mkCtx(10), bson.D{}) if err != nil { return []string{}, err } if err = cursor.All(self.mkCtx(10), &results); err != nil { return []string{}, err } var protocols []string for _, t := range results { protocols = append(protocols, fmt.Sprintf("%s.%s", t.Name, t.Passcode)) } return protocols, nil } return []string{}, errors.New(fmt.Sprintf(errNoCollection, "tables")) } func (self *DbEngine) InsertDiceRoll(table models.TableKey, diceRoll models.DiceRoll) error { tables := self.db.Collection("tables") if tables != nil { var result models.Table err := tables.FindOneAndUpdate( self.mkCtx(10), bson.D{ {"name", table.Name}, {"passcode", table.Passcode}, }, bson.D{ {"$push", bson.D{ {"diceRolls", bson.D{ {"$each", []models.DiceRoll{diceRoll}}, {"$slice", 1000}, }}, }}, }, ).Decode(&result) return err } return errors.New(fmt.Sprintf(errNoCollection, "tables")) } func (self *DbEngine) GetDiceRolls(table models.TableKey) ([]models.DiceRoll, error) { tables := self.db.Collection("tables") if tables != nil { var result models.Table err := tables.FindOne( self.mkCtx(10), bson.D{ {"name", table.Name}, {"passcode", table.Passcode}, }).Decode(&result) if err == nil { return result.DiceRolls, nil } else { return nil, errors.New(fmt.Sprintf(errNoDocument, table.Name, "tables")) } } return nil, errors.New(fmt.Sprintf(errNoCollection, "tables")) } func (self *DbEngine) SetMapImageUrl(table models.TableKey, url string) error { tables := self.db.Collection("tables") if tables != nil { var result models.Table err := tables.FindOneAndUpdate( self.mkCtx(10), bson.D{ {"name", table.Name}, {"passcode", table.Passcode}, }, bson.D{ {"$set", bson.D{{"mapImageUrl", url}}}, }, ).Decode(&result) return err } return errors.New(fmt.Sprintf(errNoCollection, "tables")) } func (self *DbEngine) GetMapImageUrl(table models.TableKey) (string, error) { tables := self.db.Collection("tables") if tables != nil { var result models.Table err := tables.FindOne( self.mkCtx(10), bson.D{ {"name", table.Name}, {"passcode", table.Passcode}, }).Decode(&result) if err == nil { return result.MapImageUrl, nil } else { return "", errors.New(fmt.Sprintf(errNoDocument, table.Name, "tables")) } } return "", errors.New(fmt.Sprintf(errNoCollection, "tables")) } func (self *DbEngine) SetAuxMessage(table models.TableKey, message string) error { tables := self.db.Collection("tables") if tables != nil { var result models.Table err := tables.FindOneAndUpdate( self.mkCtx(10), bson.D{ {"name", table.Name}, {"passcode", table.Passcode}, }, bson.D{ {"$set", bson.D{{"auxMessage", message}}}, }, ).Decode(&result) return err } return errors.New(fmt.Sprintf(errNoCollection, "tables")) } func (self *DbEngine) GetAuxMessage(table models.TableKey) (string, error) { tables := self.db.Collection("tables") if tables != nil { var result models.Table err := tables.FindOne( self.mkCtx(10), bson.D{ {"name", table.Name}, {"passcode", table.Passcode}, }).Decode(&result) if err == nil { return result.AuxMessage, nil } else { return "", errors.New(fmt.Sprintf(errNoDocument, table.Name, "tables")) } } return "", errors.New(fmt.Sprintf(errNoCollection, "tables")) } func (self *DbEngine) CheckToken(table models.TableKey, tokenId string) (bool, bool) { mongoId, err := primitive.ObjectIDFromHex(tokenId) if err != nil { return false, false } tables := self.db.Collection("tables") if tables != nil { result := models.Table{} err := tables.FindOne(self.mkCtx(10), bson.D{ {"name", table.Name}, {"passcode", table.Passcode}, {"tokens", bson.E{"_id", mongoId}}, }).Decode(&result) if err != nil { return false, false } else { active := false for _, t := range result.Tokens { if *t.Id == tokenId && t.Active { active = true } } return true, active } } return false, false } func (self *DbEngine) CreateToken(table models.TableKey, token models.Token) (string, error) { tables := self.db.Collection("tables") id := primitive.NewObjectID().Hex() token.Id = &id if tables != nil { var result models.Table err := tables.FindOneAndUpdate( self.mkCtx(10), bson.D{ {"name", table.Name}, {"passcode", table.Passcode}, }, bson.D{ {"$push", bson.D{{"tokens", token}}}, }, ).Decode(&result) if err == nil { return id, err } else { return "", err } } return "", errors.New(fmt.Sprintf(errNoCollection, "tables")) } func (self *DbEngine) ActivateToken(table models.TableKey, tokenId string, active bool) error { mongoId, err := primitive.ObjectIDFromHex(tokenId) if err != nil { return err } tables := self.db.Collection("tables") if tables != nil { var result models.Table err := tables.FindOneAndUpdate( self.mkCtx(10), bson.D{ {"name", table.Name}, {"passcode", table.Passcode}, {"tokens", bson.E{"_id", mongoId}}, }, bson.D{ {"$set", bson.D{{"tokens.$", bson.D{ {"active", active}, }}}}, }, ).Decode(&result) return err } return errors.New(fmt.Sprintf(errNoCollection, "tables")) } func (self *DbEngine) MoveToken(table models.TableKey, token models.Token) error { mongoId, err := primitive.ObjectIDFromHex(*token.Id) if err != nil { return err } tables := self.db.Collection("tables") if tables != nil { var result models.Table err := tables.FindOneAndUpdate( self.mkCtx(10), bson.D{ {"name", table.Name}, {"passcode", table.Passcode}, {"tokens", bson.E{"_id", mongoId}}, }, bson.D{ {"$set", bson.D{{"tokens.$", bson.D{ {"x", token.X}, {"y", token.Y}, }}}}, }, ).Decode(&result) return err } return errors.New(fmt.Sprintf(errNoCollection, "tables")) } func (self *DbEngine) DestroyToken(table models.TableKey, tokenId string) error { mongoId, err := primitive.ObjectIDFromHex(tokenId) if err != nil { return err } tables := self.db.Collection("tables") if tables != nil { var result models.Table err := tables.FindOneAndUpdate( self.mkCtx(10), bson.D{ {"name", table.Name}, {"passcode", table.Passcode}, }, bson.D{ {"$pull", bson.D{{"tokens", bson.D{{"_id", mongoId}}}}}, }, ).Decode(&result) return err } return errors.New(fmt.Sprintf(errNoCollection, "tables")) } func (self *DbEngine) GetTokens(table models.TableKey, activeOnly bool) ([]models.Token, error) { tables := self.db.Collection("tables") if tables != nil { var result models.Table err := tables.FindOne( self.mkCtx(10), bson.D{ {"name", table.Name}, {"passcode", table.Passcode}, }).Decode(&result) if err == nil { tokens := []models.Token{} for _, t := range result.Tokens { if !activeOnly || t.Active { tokens = append(tokens, t) } } return tokens, nil } else { return nil, errors.New(fmt.Sprintf(errNoDocument, table.Name, "tables")) } } return nil, errors.New(fmt.Sprintf(errNoCollection, "tables")) }