000ce85816
Backend: - internal/bot/auth.go: bot token generation and verification - internal/bot/handlers.go: bot CRUD (create, list, get, update, delete, server management, token regen) - internal/bot/commands.go: slash command registration and management - internal/webhook/handlers.go: webhook CRUD and execution endpoint - internal/webhook/token.go: webhook token generation - internal/db/db.go: bots, bot_servers, slash_commands, webhooks tables - internal/gateway/events.go: BOT_JOIN, BOT_LEAVE event constants - cmd/server/main.go: wired bot, webhook, invite routes Frontend: - stores/bot.ts: Zustand store for bot management - BotManager.tsx: bot list, create, edit, delete, add to server, token display - CommandManager.tsx: slash command CRUD per bot - SlashCommandPopup.tsx: / command autocomplete popup - App.tsx: /bots and /bots/:id/commands routes Examples: - examples/modbot/: auto-delete banned words, /kick, /ban, /purge commands - examples/welcome/: welcome message on member join
187 lines
6.0 KiB
Go
187 lines
6.0 KiB
Go
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,
|
|
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,
|
|
reply_to UUID REFERENCES messages(id) ON DELETE SET 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);
|
|
|
|
CREATE TABLE IF NOT EXISTS reactions (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
message_id UUID NOT NULL REFERENCES messages(id) ON DELETE CASCADE,
|
|
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
emoji VARCHAR(64) NOT NULL,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
UNIQUE (message_id, user_id, emoji)
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_reactions_message ON reactions(message_id);
|
|
|
|
CREATE TABLE IF NOT EXISTS invites (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
code VARCHAR(16) NOT NULL UNIQUE,
|
|
server_id UUID NOT NULL REFERENCES servers(id) ON DELETE CASCADE,
|
|
created_by UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
expires_at TIMESTAMPTZ,
|
|
max_uses INTEGER,
|
|
use_count INTEGER NOT NULL DEFAULT 0,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_invites_code ON invites(code);
|
|
|
|
CREATE TABLE IF NOT EXISTS bots (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
name VARCHAR(100) NOT NULL,
|
|
avatar TEXT,
|
|
description TEXT DEFAULT '',
|
|
owner_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
token VARCHAR(64) NOT NULL UNIQUE,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS bot_servers (
|
|
bot_id UUID NOT NULL REFERENCES bots(id) ON DELETE CASCADE,
|
|
server_id UUID NOT NULL REFERENCES servers(id) ON DELETE CASCADE,
|
|
added_by UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
added_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
PRIMARY KEY (bot_id, server_id)
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS slash_commands (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
bot_id UUID NOT NULL REFERENCES bots(id) ON DELETE CASCADE,
|
|
server_id UUID NOT NULL REFERENCES servers(id) ON DELETE CASCADE,
|
|
name VARCHAR(32) NOT NULL,
|
|
description TEXT DEFAULT '',
|
|
options JSONB DEFAULT '[]',
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
UNIQUE (server_id, name)
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS webhooks (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
channel_id UUID NOT NULL REFERENCES channels(id) ON DELETE CASCADE,
|
|
name VARCHAR(100) NOT NULL,
|
|
token VARCHAR(64) NOT NULL,
|
|
avatar TEXT,
|
|
created_by UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_bots_owner ON bots(owner_id);
|
|
CREATE INDEX IF NOT EXISTS idx_bots_token ON bots(token);
|
|
CREATE INDEX IF NOT EXISTS idx_bot_servers_bot ON bot_servers(bot_id);
|
|
CREATE INDEX IF NOT EXISTS idx_slash_commands_server ON slash_commands(server_id, name);
|
|
CREATE INDEX IF NOT EXISTS idx_webhooks_channel ON webhooks(channel_id);
|
|
CREATE INDEX IF NOT EXISTS idx_webhooks_token ON webhooks(token);
|
|
`
|