Migrations and DB optimization

This commit is contained in:
Atridad Lahiji 2024-06-28 23:58:36 -06:00
parent 4ad17de80a
commit 2ade3e1380
No known key found for this signature in database
4 changed files with 95 additions and 35 deletions

View file

@ -29,6 +29,7 @@ func RegisterUserHandler(c echo.Context) error {
user := lib.User{Name: name, Email: email, Password: hashedPassword} user := lib.User{Name: name, Email: email, Password: hashedPassword}
println("User: ", user.Name, user.Email, user.Password) println("User: ", user.Name, user.Email, user.Password)
if err := lib.SaveUser(lib.GetDBPool(), &user); err != nil { if err := lib.SaveUser(lib.GetDBPool(), &user); err != nil {
println("Error saving user: ", err.Error())
return c.JSON(http.StatusInternalServerError, "Failed to save user") return c.JSON(http.StatusInternalServerError, "Failed to save user")
} }

View file

@ -2,6 +2,8 @@ package lib
import ( import (
"context" "context"
"crypto/rand"
"encoding/hex"
"fmt" "fmt"
"github.com/jackc/pgx/v4/pgxpool" "github.com/jackc/pgx/v4/pgxpool"
@ -9,11 +11,24 @@ import (
var dbPool *pgxpool.Pool 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. // Initializes the global database connection pool.
func InitializeDBPool(host, user, password, dbname string, port int) error { 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) connString := fmt.Sprintf("postgres://%s:%s@%s:%d/%s", user, password, host, port, dbname)
var err error var err error
dbPool, err = pgxpool.Connect(context.Background(), connString) dbPool, err = pgxpool.Connect(context.Background(), connString)
if err != nil { if err != nil {
@ -23,13 +38,20 @@ func InitializeDBPool(host, user, password, dbname string, port int) error {
} }
func InitializeSchema(dbPool *pgxpool.Pool) error { func InitializeSchema(dbPool *pgxpool.Pool) error {
const schemaSQL = ` migrations := []Migration{
{
Version: "1_create_users_table",
SQL: `
CREATE TABLE IF NOT EXISTS users ( CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY, id TEXT NOT NULL PRIMARY KEY,
name VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL,
email VARCHAR(255) UNIQUE NOT NULL, email VARCHAR(255) UNIQUE NOT NULL,
password VARCHAR(255) NOT NULL password VARCHAR(255) NOT NULL
); );`,
},
{
Version: "2_create_rooms_table",
SQL: `
CREATE TABLE IF NOT EXISTS rooms ( CREATE TABLE IF NOT EXISTS rooms (
id TEXT NOT NULL PRIMARY KEY, id TEXT NOT NULL PRIMARY KEY,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
@ -38,14 +60,63 @@ func InitializeSchema(dbPool *pgxpool.Pool) error {
topicName TEXT, topicName TEXT,
visible BOOLEAN NOT NULL DEFAULT false, visible BOOLEAN NOT NULL DEFAULT false,
scale TEXT NOT NULL DEFAULT '0.5,1,2,3,5' scale TEXT NOT NULL DEFAULT '0.5,1,2,3,5'
); );`,
` },
// TODO: Add more tables to the schema {
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 { if err != nil {
return err 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 return nil
} }

View file

@ -2,10 +2,7 @@ package lib
import ( import (
"context" "context"
"crypto/rand"
"encoding/hex"
"errors" "errors"
"fmt"
"time" "time"
"github.com/jackc/pgx/v4/pgxpool" "github.com/jackc/pgx/v4/pgxpool"
@ -21,15 +18,6 @@ type Room struct {
Scale string 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. // CreateRoom creates a new room in the database.
func CreateRoom(dbPool *pgxpool.Pool, userID, roomName string) (*Room, error) { func CreateRoom(dbPool *pgxpool.Pool, userID, roomName string) (*Room, error) {
if dbPool == nil { if dbPool == nil {
@ -37,7 +25,7 @@ func CreateRoom(dbPool *pgxpool.Pool, userID, roomName string) (*Room, error) {
} }
// Generate a new ID for the room // Generate a new ID for the room
newRoomID := GenerateNewID() newRoomID := GenerateNewID("room")
// Insert the new room into the database // 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") _, 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")

View file

@ -65,7 +65,7 @@ func SaveUser(dbPool *pgxpool.Pool, user *User) error {
return errors.New("database connection pool is not initialized") 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 { if err != nil {
return err return err
} }