package mongodb import ( "context" "errors" "fmt" "go.mongodb.org/mongo-driver/bson" "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 = "doccument property is not an array: %s.%s" type DbAdapter interface { Init(mongoUri string) error CreateTable(table models.TableKey) error DestroyTable(table models.TableKey) error CheckTable(table models.TableKey) bool 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) AddToken(table models.TableKey, token models.Token, active bool) error RemoveToken(table models.TableKey, tokenId string, active bool) error ModifyToken(table models.TableKey, token models.Token, active bool) error GetTokens(table models.TableKey, active 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) 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{ {"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{ {"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) AddToken(table models.TableKey, token models.Token, active bool) error { tables := self.db.Collection("tables") if tables != nil { tokenArrKey := "tokens" if !active { tokenArrKey = "availableTokens" } var result models.Table err := tables.FindOneAndUpdate( self.mkCtx(10), bson.D{ {"name", table.Name}, {"passcode", table.Passcode}, }, bson.D{ {"$push", bson.D{{tokenArrKey, token}}}, }, ).Decode(&result) return err } return errors.New(fmt.Sprintf(errNoCollection, "tables")) } func (self *DbEngine) RemoveToken(table models.TableKey, tokenId string, active bool) error { tables := self.db.Collection("tables") if tables != nil { tokenArrKey := "tokens" if !active { tokenArrKey = "availableTokens" } var result models.Table err := tables.FindOneAndUpdate( self.mkCtx(10), bson.D{ {"name", table.Name}, {"passcode", table.Passcode}, }, bson.D{ {"$pull", bson.D{{tokenArrKey, bson.D{{"_id", tokenId}}}}}, }, ).Decode(&result) return err } return errors.New(fmt.Sprintf(errNoCollection, "tables")) } func (self *DbEngine) ModifyToken(table models.TableKey, token models.Token, active bool) error { tables := self.db.Collection("tables") if tables != nil { tokenArrKey := "tokens" if !active { tokenArrKey = "availableTokens" } var result models.Table err := tables.FindOneAndUpdate( self.mkCtx(10), bson.D{ {"name", table.Name}, {"passcode", table.Passcode}, {tokenArrKey, bson.E{"_id", token.Id}}, }, bson.D{ {"$set", bson.D{{tokenArrKey + ".$", bson.D{ {"name", token.Name}, {"spriteUri", token.SpriteUri}, {"x", token.X}, {"y", token.Y}, }}}}, }, ).Decode(&result) return err } return errors.New(fmt.Sprintf(errNoCollection, "tables")) } func (self *DbEngine) GetTokens(table models.TableKey, active 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 { if active { return result.Tokens, nil } else { return result.AvailableTokens, nil } } else { return nil, errors.New(fmt.Sprintf(errNoDocument, table.Name, "tables")) } } return nil, errors.New(fmt.Sprintf(errNoCollection, "tables")) }