Skip to content

Commit 29a1624

Browse files
migration service with init_schema + docker-compose deploy
1 parent b1f7764 commit 29a1624

4 files changed

Lines changed: 155 additions & 0 deletions

File tree

cmd/server/main.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ func main() {
4141
// Setup structured logging
4242
slog.SetDefault(slog.New(slog.NewJSONHandler(os.Stdout, nil)))
4343

44+
if len(os.Args) > 1 {
45+
os.Exit(runCommand(os.Args[1:]))
46+
}
47+
4448
// Load configuration
4549
cfg, err := config.Load()
4650
if err != nil {
@@ -104,3 +108,35 @@ func main() {
104108

105109
slog.Info("server stopped")
106110
}
111+
112+
func runCommand(args []string) int {
113+
if len(args) == 0 {
114+
return 0
115+
}
116+
117+
switch args[0] {
118+
case "migrate":
119+
if len(args) != 2 || args[1] != "up" {
120+
slog.Error("invalid migrate command", "usage", "capy-server migrate up")
121+
return 2
122+
}
123+
124+
cfg, err := config.Load()
125+
if err != nil {
126+
slog.Error("failed to load config", "error", err)
127+
return 1
128+
}
129+
130+
slog.Info("running migrations", "path", cfg.Database.MigrationsPath)
131+
if err := database.RunMigrations(context.Background(), cfg.Database.URL, cfg.Database.MigrationsPath); err != nil {
132+
slog.Error("migration command failed", "error", err)
133+
return 1
134+
}
135+
136+
slog.Info("migrations complete")
137+
return 0
138+
default:
139+
slog.Error("unknown command", "command", args[0], "usage", "capy-server [migrate up]")
140+
return 2
141+
}
142+
}

docker-compose.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,18 @@ services:
2424
depends_on:
2525
db:
2626
condition: service_healthy
27+
migrate:
28+
condition: service_completed_successfully
29+
30+
migrate:
31+
build: .
32+
env_file:
33+
- .env
34+
depends_on:
35+
db:
36+
condition: service_healthy
37+
command: ["migrate", "up"]
38+
restart: "no"
2739

2840
tunnel:
2941
image: cloudflare/cloudflared:latest
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
DROP TRIGGER IF EXISTS update_events_modtime ON events;
2+
DROP TRIGGER IF EXISTS update_orgs_modtime ON organizations;
3+
DROP TRIGGER IF EXISTS update_users_modtime ON users;
4+
5+
DROP INDEX IF EXISTS idx_bot_tokens_active;
6+
7+
DROP TABLE IF EXISTS bot_tokens;
8+
DROP TABLE IF EXISTS event_registrations;
9+
DROP TABLE IF EXISTS event_hosting;
10+
DROP TABLE IF EXISTS events;
11+
DROP TABLE IF EXISTS org_members;
12+
DROP TABLE IF EXISTS organizations;
13+
DROP TABLE IF EXISTS users;
14+
15+
DROP FUNCTION IF EXISTS update_modified_column();
16+
DROP TYPE IF EXISTS user_role;
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
-- schema.sql
2+
-- Database Schema for CAPY (Club Assistant in Python)
3+
4+
-- 1. ENUMs & Functions
5+
CREATE TYPE user_role AS ENUM ('student', 'alumni', 'faculty', 'external');
6+
7+
CREATE OR REPLACE FUNCTION update_modified_column()
8+
RETURNS TRIGGER AS $$
9+
BEGIN
10+
NEW.date_modified = CURRENT_DATE;
11+
RETURN NEW;
12+
END;
13+
$$ language 'plpgsql';
14+
15+
-- 2. Tables
16+
CREATE TABLE IF NOT EXISTS users (
17+
uid UUID PRIMARY KEY DEFAULT gen_random_uuid(),
18+
first_name TEXT NOT NULL,
19+
last_name TEXT NOT NULL,
20+
personal_email TEXT UNIQUE,
21+
school_email TEXT UNIQUE,
22+
phone TEXT,
23+
grad_year INT,
24+
role user_role DEFAULT 'student',
25+
date_created DATE DEFAULT CURRENT_DATE,
26+
date_modified DATE DEFAULT CURRENT_DATE
27+
);
28+
29+
CREATE TABLE IF NOT EXISTS organizations (
30+
oid UUID PRIMARY KEY DEFAULT gen_random_uuid(),
31+
name TEXT NOT NULL,
32+
date_created DATE DEFAULT CURRENT_DATE,
33+
date_modified DATE DEFAULT CURRENT_DATE
34+
);
35+
36+
CREATE TABLE IF NOT EXISTS org_members (
37+
uid UUID REFERENCES users(uid) ON DELETE CASCADE,
38+
oid UUID REFERENCES organizations(oid) ON DELETE CASCADE,
39+
is_admin BOOLEAN DEFAULT FALSE,
40+
date_joined DATE DEFAULT CURRENT_DATE,
41+
last_active DATE DEFAULT CURRENT_DATE,
42+
PRIMARY KEY (uid, oid)
43+
);
44+
45+
CREATE TABLE IF NOT EXISTS events (
46+
eid UUID PRIMARY KEY DEFAULT gen_random_uuid(),
47+
location TEXT,
48+
event_time TIMESTAMP,
49+
description TEXT,
50+
date_created DATE DEFAULT CURRENT_DATE,
51+
date_modified DATE DEFAULT CURRENT_DATE
52+
);
53+
54+
CREATE TABLE IF NOT EXISTS event_hosting (
55+
eid UUID REFERENCES events(eid) ON DELETE CASCADE,
56+
oid UUID REFERENCES organizations(oid) ON DELETE CASCADE,
57+
PRIMARY KEY (eid, oid)
58+
);
59+
60+
CREATE TABLE IF NOT EXISTS event_registrations (
61+
uid UUID REFERENCES users(uid) ON DELETE CASCADE,
62+
eid UUID REFERENCES events(eid) ON DELETE CASCADE,
63+
is_attending BOOLEAN DEFAULT FALSE,
64+
is_admin BOOLEAN DEFAULT FALSE,
65+
date_registered DATE DEFAULT CURRENT_DATE,
66+
PRIMARY KEY (uid, eid)
67+
);
68+
69+
-- 3. Bot Tokens (global access for M2M authentication)
70+
CREATE TABLE IF NOT EXISTS bot_tokens (
71+
token_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
72+
token_hash TEXT NOT NULL, -- bcrypt hash of the token
73+
name TEXT NOT NULL, -- human-readable name for the bot
74+
created_by UUID NOT NULL REFERENCES users(uid),
75+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
76+
last_used_at TIMESTAMP,
77+
expires_at TIMESTAMP, -- NULL = never expires
78+
is_active BOOLEAN DEFAULT TRUE
79+
);
80+
81+
CREATE INDEX IF NOT EXISTS idx_bot_tokens_active ON bot_tokens(is_active) WHERE is_active = TRUE;
82+
83+
-- 4. Triggers
84+
DROP TRIGGER IF EXISTS update_users_modtime ON users;
85+
CREATE TRIGGER update_users_modtime BEFORE UPDATE ON users FOR EACH ROW EXECUTE FUNCTION update_modified_column();
86+
87+
DROP TRIGGER IF EXISTS update_orgs_modtime ON organizations;
88+
CREATE TRIGGER update_orgs_modtime BEFORE UPDATE ON organizations FOR EACH ROW EXECUTE FUNCTION update_modified_column();
89+
90+
DROP TRIGGER IF EXISTS update_events_modtime ON events;
91+
CREATE TRIGGER update_events_modtime BEFORE UPDATE ON events FOR EACH ROW EXECUTE FUNCTION update_modified_column();

0 commit comments

Comments
 (0)