From 2ade3e1380c180146bb1bb364f93d7b20d91fc6f Mon Sep 17 00:00:00 2001 From: Atridad Lahiji Date: Fri, 28 Jun 2024 23:58:36 -0600 Subject: [PATCH] Migrations and DB optimization --- api/register.go | 1 + lib/db.go | 113 +++++++++++++++++++++++++++++++-------- lib/room.go | 14 +---- lib/{auth.go => user.go} | 2 +- 4 files changed, 95 insertions(+), 35 deletions(-) rename lib/{auth.go => user.go} (95%) diff --git a/api/register.go b/api/register.go index 38a5d99..a208bc4 100644 --- a/api/register.go +++ b/api/register.go @@ -29,6 +29,7 @@ func RegisterUserHandler(c echo.Context) error { user := lib.User{Name: name, Email: email, Password: hashedPassword} println("User: ", user.Name, user.Email, user.Password) if err := lib.SaveUser(lib.GetDBPool(), &user); err != nil { + println("Error saving user: ", err.Error()) return c.JSON(http.StatusInternalServerError, "Failed to save user") } diff --git a/lib/db.go b/lib/db.go index b6c71d0..a84ec92 100644 --- a/lib/db.go +++ b/lib/db.go @@ -2,6 +2,8 @@ package lib import ( "context" + "crypto/rand" + "encoding/hex" "fmt" "github.com/jackc/pgx/v4/pgxpool" @@ -9,11 +11,24 @@ import ( var dbPool *pgxpool.Pool +// Migration represents a database migration. +type Migration struct { + Version string + SQL string +} + +// GenerateNewID generates a DB with the format "prefix_randomstring". +func GenerateNewID(prefix string) string { + randomBytes := make([]byte, 16) + if _, err := rand.Read(randomBytes); err != nil { + panic(err) + } + return fmt.Sprintf("%s_%s", prefix, hex.EncodeToString(randomBytes)) +} + // Initializes the global database connection pool. func InitializeDBPool(host, user, password, dbname string, port int) error { - // Construct the connection string using the provided parameters connString := fmt.Sprintf("postgres://%s:%s@%s:%d/%s", user, password, host, port, dbname) - var err error dbPool, err = pgxpool.Connect(context.Background(), connString) if err != nil { @@ -23,29 +38,85 @@ func InitializeDBPool(host, user, password, dbname string, port int) error { } func InitializeSchema(dbPool *pgxpool.Pool) error { - const schemaSQL = ` - CREATE TABLE IF NOT EXISTS users ( - id SERIAL PRIMARY KEY, - name VARCHAR(255) NOT NULL, - email VARCHAR(255) UNIQUE NOT NULL, - password VARCHAR(255) NOT NULL - ); - CREATE TABLE IF NOT EXISTS rooms ( - id TEXT NOT NULL PRIMARY KEY, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - userId TEXT NOT NULL, - roomName TEXT, - topicName TEXT, - visible BOOLEAN NOT NULL DEFAULT false, - scale TEXT NOT NULL DEFAULT '0.5,1,2,3,5' - ); - ` - // TODO: Add more tables to the schema + migrations := []Migration{ + { + Version: "1_create_users_table", + SQL: ` + CREATE TABLE IF NOT EXISTS users ( + id TEXT NOT NULL PRIMARY KEY, + name VARCHAR(255) NOT NULL, + email VARCHAR(255) UNIQUE NOT NULL, + password VARCHAR(255) NOT NULL + );`, + }, + { + Version: "2_create_rooms_table", + SQL: ` + CREATE TABLE IF NOT EXISTS rooms ( + id TEXT NOT NULL PRIMARY KEY, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + userId TEXT NOT NULL, + roomName TEXT, + topicName TEXT, + visible BOOLEAN NOT NULL DEFAULT false, + scale TEXT NOT NULL DEFAULT '0.5,1,2,3,5' + );`, + }, + { + Version: "3_add_foreign_key_to_rooms", + SQL: ` + ALTER TABLE rooms + ADD CONSTRAINT fk_user_id + FOREIGN KEY (userId) REFERENCES users(id);`, + }, + { + Version: "4_create_index_on_users_email", + SQL: ` + CREATE INDEX IF NOT EXISTS idx_users_email ON users(email); + `, + }, + { + Version: "5_create_index_on_rooms_userid", + SQL: ` + CREATE INDEX IF NOT EXISTS idx_rooms_userid ON rooms(userId); + `, + }, + } - _, err := dbPool.Exec(context.Background(), schemaSQL) + // Ensure the migrations table exists + _, err := dbPool.Exec(context.Background(), ` + CREATE TABLE IF NOT EXISTS migrations ( + version VARCHAR(255) PRIMARY KEY + ); + `) if err != nil { return err } + + // Apply each migration + for _, migration := range migrations { + // Check if this migration has already been applied + var exists bool + err := dbPool.QueryRow(context.Background(), "SELECT EXISTS(SELECT 1 FROM migrations WHERE version = $1)", migration.Version).Scan(&exists) + if err != nil { + return err + } + + if !exists { + // Apply the migration + _, err = dbPool.Exec(context.Background(), migration.SQL) + if err != nil { + return err + } + + // Mark this migration as applied + _, err = dbPool.Exec(context.Background(), "INSERT INTO migrations (version) VALUES ($1)", migration.Version) + if err != nil { + return err + } + } + } + return nil } diff --git a/lib/room.go b/lib/room.go index 6780cd5..fbc6c3b 100644 --- a/lib/room.go +++ b/lib/room.go @@ -2,10 +2,7 @@ package lib import ( "context" - "crypto/rand" - "encoding/hex" "errors" - "fmt" "time" "github.com/jackc/pgx/v4/pgxpool" @@ -21,15 +18,6 @@ type Room struct { Scale string } -// GenerateNewID generates a new room ID with the format "room_randomstring". -func GenerateNewID() string { - randomBytes := make([]byte, 16) - if _, err := rand.Read(randomBytes); err != nil { - panic(err) - } - return fmt.Sprintf("room_%s", hex.EncodeToString(randomBytes)) -} - // CreateRoom creates a new room in the database. func CreateRoom(dbPool *pgxpool.Pool, userID, roomName string) (*Room, error) { if dbPool == nil { @@ -37,7 +25,7 @@ func CreateRoom(dbPool *pgxpool.Pool, userID, roomName string) (*Room, error) { } // Generate a new ID for the room - newRoomID := GenerateNewID() + newRoomID := GenerateNewID("room") // Insert the new room into the database _, err := dbPool.Exec(context.Background(), "INSERT INTO rooms (id, userid, roomname, topicname, visible, scale) VALUES ($1, $2, $3, $4, $5, $6)", newRoomID, userID, roomName, "My First Topic", false, "0.5,1,2,3,5") diff --git a/lib/auth.go b/lib/user.go similarity index 95% rename from lib/auth.go rename to lib/user.go index 98d9113..196b3f3 100644 --- a/lib/auth.go +++ b/lib/user.go @@ -65,7 +65,7 @@ func SaveUser(dbPool *pgxpool.Pool, user *User) error { return errors.New("database connection pool is not initialized") } - commandTag, err := dbPool.Exec(context.Background(), "INSERT INTO users (name, email, password) VALUES ($1, $2, $3)", user.Name, user.Email, user.Password) + commandTag, err := dbPool.Exec(context.Background(), "INSERT INTO users (id, name, email, password) VALUES ($1, $2, $3, $4)", GenerateNewID("user"), user.Name, user.Email, user.Password) if err != nil { return err }