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

113
lib/db.go
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,29 +38,85 @@ 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{
CREATE TABLE IF NOT EXISTS users ( {
id SERIAL PRIMARY KEY, Version: "1_create_users_table",
name VARCHAR(255) NOT NULL, SQL: `
email VARCHAR(255) UNIQUE NOT NULL, CREATE TABLE IF NOT EXISTS users (
password VARCHAR(255) NOT NULL id TEXT NOT NULL PRIMARY KEY,
); name VARCHAR(255) NOT NULL,
CREATE TABLE IF NOT EXISTS rooms ( email VARCHAR(255) UNIQUE NOT NULL,
id TEXT NOT NULL PRIMARY KEY, password VARCHAR(255) NOT NULL
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, );`,
userId TEXT NOT NULL, },
roomName TEXT, {
topicName TEXT, Version: "2_create_rooms_table",
visible BOOLEAN NOT NULL DEFAULT false, SQL: `
scale TEXT NOT NULL DEFAULT '0.5,1,2,3,5' CREATE TABLE IF NOT EXISTS rooms (
); id TEXT NOT NULL PRIMARY KEY,
` created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
// TODO: Add more tables to the schema 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 { 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
} }