package db import ( "context" "database/sql" "fmt" "git.dustin.coffee/hobokenchicken/dumpsterChat/internal/config" _ "github.com/jackc/pgx/v5/stdlib" ) type DB struct { *sql.DB } func New(cfg *config.Config) (*DB, error) { db, err := sql.Open("pgx", cfg.DatabaseDSN()) if err != nil { return nil, fmt.Errorf("open database: %w", err) } if err := db.Ping(); err != nil { return nil, fmt.Errorf("ping database: %w", err) } return &DB{db}, nil } func (d *DB) RunMigrations() error { _, err := d.ExecContext(context.Background(), migrationsSQL) return err } const migrationsSQL = ` CREATE TABLE IF NOT EXISTS users ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), username VARCHAR(32) NOT NULL UNIQUE, display_name VARCHAR(32), email VARCHAR(255) NOT NULL UNIQUE, password_hash VARCHAR(255), avatar TEXT, status VARCHAR(16) NOT NULL DEFAULT 'offline', created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE TABLE IF NOT EXISTS sessions ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, token VARCHAR(255) NOT NULL UNIQUE, expires_at TIMESTAMPTZ NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE TABLE IF NOT EXISTS servers ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), name VARCHAR(100) NOT NULL, icon TEXT, owner_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE TABLE IF NOT EXISTS channels ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), server_id UUID NOT NULL REFERENCES servers(id) ON DELETE CASCADE, name VARCHAR(100) NOT NULL, type VARCHAR(16) NOT NULL DEFAULT 'text', category VARCHAR(100) NOT NULL DEFAULT 'general', position INTEGER NOT NULL DEFAULT 0, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), UNIQUE (server_id, name) ); CREATE TABLE IF NOT EXISTS members ( user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, server_id UUID NOT NULL REFERENCES servers(id) ON DELETE CASCADE, joined_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), PRIMARY KEY (user_id, server_id) ); CREATE TABLE IF NOT EXISTS messages ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), channel_id UUID NOT NULL REFERENCES channels(id) ON DELETE CASCADE, author_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, content TEXT NOT NULL, edited_at TIMESTAMPTZ, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE TABLE IF NOT EXISTS roles ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), server_id UUID NOT NULL REFERENCES servers(id) ON DELETE CASCADE, name VARCHAR(100) NOT NULL, color VARCHAR(7), permissions BIGINT NOT NULL DEFAULT 0, position INTEGER NOT NULL DEFAULT 0, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE TABLE IF NOT EXISTS member_roles ( user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, server_id UUID NOT NULL REFERENCES servers(id) ON DELETE CASCADE, role_id UUID NOT NULL REFERENCES roles(id) ON DELETE CASCADE, PRIMARY KEY (user_id, role_id) ); CREATE INDEX IF NOT EXISTS idx_messages_channel_created ON messages(channel_id, created_at DESC); CREATE INDEX IF NOT EXISTS idx_sessions_token ON sessions(token); CREATE INDEX IF NOT EXISTS idx_members_server_user ON members(server_id, user_id); `