Files
dumpsterChat/cmd/migrate/main.go
T

154 lines
4.3 KiB
Go

package main
import (
"database/sql"
"flag"
"fmt"
"log"
"os"
"git.dustin.coffee/hobokenchicken/dumpsterChat/internal/config"
_ "github.com/jackc/pgx/v5/stdlib"
)
func main() {
direction := flag.String("direction", "up", "migration direction: up or down")
flag.Parse()
cfg := config.Load()
db, err := sql.Open("pgx", cfg.DatabaseDSN())
if err != nil {
log.Fatalf("failed to open database: %v", err)
}
defer db.Close()
if err := db.Ping(); err != nil {
log.Fatalf("failed to ping database: %v", err)
}
switch *direction {
case "up":
fmt.Println("running migrations up...")
if err := runUp(db); err != nil {
log.Fatalf("migration failed: %v", err)
}
fmt.Println("migrations complete")
case "down":
fmt.Println("running migrations down...")
if err := runDown(db); err != nil {
log.Fatalf("migration failed: %v", err)
}
fmt.Println("migrations complete")
default:
fmt.Fprintf(os.Stderr, "unknown direction: %s\n", *direction)
os.Exit(1)
}
}
const upSQL = `
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,
bio VARCHAR(250) DEFAULT '',
accent_color VARCHAR(7) DEFAULT '',
status VARCHAR(16) NOT NULL DEFAULT 'offline',
status_text VARCHAR(128) DEFAULT '',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_users_username ON users(username);
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,
pinned BOOLEAN NOT NULL DEFAULT FALSE,
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);
ALTER TABLE messages ADD COLUMN IF NOT EXISTS pinned BOOLEAN NOT NULL DEFAULT FALSE;
`
const downSQL = `
DROP TABLE IF EXISTS member_roles;
DROP TABLE IF EXISTS roles;
DROP TABLE IF EXISTS messages;
DROP TABLE IF EXISTS members;
DROP TABLE IF EXISTS channels;
DROP TABLE IF EXISTS servers;
DROP TABLE IF EXISTS sessions;
DROP TABLE IF EXISTS users;
`
func runUp(db *sql.DB) error {
_, err := db.Exec(upSQL)
return err
}
func runDown(db *sql.DB) error {
_, err := db.Exec(downSQL)
return err
}