···66 email TEXT,
77 photo TEXT,
88 url TEXT,
99+ status TEXT NOT NULL DEFAULT 'active' CHECK(status IN ('active', 'suspended', 'inactive')),
1010+ role TEXT NOT NULL DEFAULT 'user',
911 is_admin INTEGER NOT NULL DEFAULT 0,
1012 created_at INTEGER NOT NULL DEFAULT (strftime('%s','now'))
1113);
···4547 used INTEGER NOT NULL DEFAULT 0,
4648 used_by INTEGER,
4749 used_at INTEGER,
5050+ max_uses INTEGER DEFAULT 1,
5151+ current_uses INTEGER NOT NULL DEFAULT 0,
5252+ expires_at INTEGER,
5353+ note TEXT,
5454+ message TEXT,
4855 created_at INTEGER NOT NULL DEFAULT (strftime('%s','now')),
4956 FOREIGN KEY (created_by) REFERENCES users(id) ON DELETE CASCADE,
5057 FOREIGN KEY (used_by) REFERENCES users(id) ON DELETE SET NULL
5158);
52596060+CREATE TABLE IF NOT EXISTS apps (
6161+ id INTEGER PRIMARY KEY AUTOINCREMENT,
6262+ client_id TEXT NOT NULL UNIQUE,
6363+ redirect_uris TEXT NOT NULL,
6464+ name TEXT,
6565+ logo_url TEXT,
6666+ description TEXT,
6767+ is_preregistered INTEGER NOT NULL DEFAULT 0,
6868+ client_secret_hash TEXT,
6969+ available_roles TEXT,
7070+ default_role TEXT,
7171+ first_seen INTEGER NOT NULL DEFAULT (strftime('%s','now')),
7272+ last_used INTEGER NOT NULL DEFAULT (strftime('%s','now'))
7373+);
7474+7575+CREATE TABLE IF NOT EXISTS permissions (
7676+ id INTEGER PRIMARY KEY AUTOINCREMENT,
7777+ user_id INTEGER NOT NULL,
7878+ client_id TEXT NOT NULL,
7979+ scopes TEXT NOT NULL,
8080+ role TEXT,
8181+ granted_at INTEGER NOT NULL DEFAULT (strftime('%s','now')),
8282+ last_used INTEGER NOT NULL DEFAULT (strftime('%s','now')),
8383+ FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
8484+ FOREIGN KEY (client_id) REFERENCES apps(client_id) ON DELETE CASCADE,
8585+ UNIQUE(user_id, client_id)
8686+);
8787+8888+CREATE TABLE IF NOT EXISTS authcodes (
8989+ id INTEGER PRIMARY KEY AUTOINCREMENT,
9090+ code TEXT NOT NULL UNIQUE,
9191+ user_id INTEGER NOT NULL,
9292+ client_id TEXT NOT NULL,
9393+ redirect_uri TEXT NOT NULL,
9494+ scopes TEXT NOT NULL,
9595+ code_challenge TEXT NOT NULL,
9696+ code_challenge_method TEXT NOT NULL DEFAULT 'S256',
9797+ expires_at INTEGER NOT NULL,
9898+ used INTEGER NOT NULL DEFAULT 0,
9999+ created_at INTEGER NOT NULL DEFAULT (strftime('%s','now')),
100100+ FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
101101+);
102102+103103+CREATE TABLE IF NOT EXISTS invite_roles (
104104+ id INTEGER PRIMARY KEY AUTOINCREMENT,
105105+ invite_id INTEGER NOT NULL,
106106+ app_id INTEGER NOT NULL,
107107+ role TEXT NOT NULL,
108108+ created_at INTEGER NOT NULL DEFAULT (strftime('%s','now')),
109109+ FOREIGN KEY (invite_id) REFERENCES invites(id) ON DELETE CASCADE,
110110+ FOREIGN KEY (app_id) REFERENCES apps(id) ON DELETE CASCADE,
111111+ UNIQUE(invite_id, app_id)
112112+);
113113+114114+CREATE TABLE IF NOT EXISTS invite_uses (
115115+ id INTEGER PRIMARY KEY AUTOINCREMENT,
116116+ invite_id INTEGER NOT NULL,
117117+ user_id INTEGER NOT NULL,
118118+ used_at INTEGER NOT NULL DEFAULT (strftime('%s','now')),
119119+ FOREIGN KEY (invite_id) REFERENCES invites(id) ON DELETE CASCADE,
120120+ FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
121121+);
122122+53123CREATE INDEX IF NOT EXISTS idx_sessions_token ON sessions(token);
54124CREATE INDEX IF NOT EXISTS idx_sessions_expires_at ON sessions(expires_at);
55125CREATE INDEX IF NOT EXISTS idx_challenges_challenge ON challenges(challenge);
56126CREATE INDEX IF NOT EXISTS idx_challenges_expires_at ON challenges(expires_at);
57127CREATE INDEX IF NOT EXISTS idx_credentials_user_id ON credentials(user_id);
58128CREATE INDEX IF NOT EXISTS idx_invites_code ON invites(code);
129129+CREATE INDEX IF NOT EXISTS idx_apps_client_id ON apps(client_id);
130130+CREATE INDEX IF NOT EXISTS idx_permissions_user_id ON permissions(user_id);
131131+CREATE INDEX IF NOT EXISTS idx_permissions_client_id ON permissions(client_id);
132132+CREATE INDEX IF NOT EXISTS idx_authcodes_code ON authcodes(code);
133133+CREATE INDEX IF NOT EXISTS idx_authcodes_expires_at ON authcodes(expires_at);
134134+CREATE INDEX IF NOT EXISTS idx_invite_roles_invite_id ON invite_roles(invite_id);
135135+CREATE INDEX IF NOT EXISTS idx_invite_roles_app_id ON invite_roles(app_id);
136136+CREATE INDEX IF NOT EXISTS idx_invite_uses_invite_id ON invite_uses(invite_id);
137137+CREATE INDEX IF NOT EXISTS idx_invite_uses_user_id ON invite_uses(user_id);
-5
src/migrations/002_add_user_status_role.sql
···11--- Add status and role columns to users table
22-ALTER TABLE users ADD COLUMN status TEXT NOT NULL DEFAULT 'active' CHECK(status IN ('active', 'suspended', 'inactive'));
33-ALTER TABLE users ADD COLUMN role TEXT NOT NULL DEFAULT 'user';
44--- Update existing admin users to have 'admin' role
55-UPDATE users SET role = 'admin' WHERE is_admin = 1;
-43
src/migrations/003_add_indieauth_tables.sql
···11--- Add tables for IndieAuth/OAuth 2.0 support
22--- Apps (auto-registered on first authorization request)
33-CREATE TABLE IF NOT EXISTS apps (
44- id INTEGER PRIMARY KEY AUTOINCREMENT,
55- client_id TEXT NOT NULL UNIQUE,
66- redirect_uris TEXT NOT NULL, -- JSON array
77- name TEXT,
88- first_seen INTEGER NOT NULL DEFAULT (strftime('%s','now')),
99- last_used INTEGER NOT NULL DEFAULT (strftime('%s','now'))
1010-);
1111--- User permissions per app
1212-CREATE TABLE IF NOT EXISTS permissions (
1313- id INTEGER PRIMARY KEY AUTOINCREMENT,
1414- user_id INTEGER NOT NULL,
1515- client_id TEXT NOT NULL,
1616- scopes TEXT NOT NULL, -- JSON array
1717- granted_at INTEGER NOT NULL DEFAULT (strftime('%s','now')),
1818- last_used INTEGER NOT NULL DEFAULT (strftime('%s','now')),
1919- FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
2020- FOREIGN KEY (client_id) REFERENCES apps(client_id) ON DELETE CASCADE,
2121- UNIQUE(user_id, client_id)
2222-);
2323--- Authorization codes (short-lived, single-use)
2424-CREATE TABLE IF NOT EXISTS authcodes (
2525- id INTEGER PRIMARY KEY AUTOINCREMENT,
2626- code TEXT NOT NULL UNIQUE,
2727- user_id INTEGER NOT NULL,
2828- client_id TEXT NOT NULL,
2929- redirect_uri TEXT NOT NULL,
3030- scopes TEXT NOT NULL, -- JSON array
3131- code_challenge TEXT NOT NULL,
3232- code_challenge_method TEXT NOT NULL DEFAULT 'S256',
3333- expires_at INTEGER NOT NULL,
3434- used INTEGER NOT NULL DEFAULT 0,
3535- created_at INTEGER NOT NULL DEFAULT (strftime('%s','now')),
3636- FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
3737-);
3838--- Indexes
3939-CREATE INDEX IF NOT EXISTS idx_apps_client_id ON apps(client_id);
4040-CREATE INDEX IF NOT EXISTS idx_permissions_user_id ON permissions(user_id);
4141-CREATE INDEX IF NOT EXISTS idx_permissions_client_id ON permissions(client_id);
4242-CREATE INDEX IF NOT EXISTS idx_authcodes_code ON authcodes(code);
4343-CREATE INDEX IF NOT EXISTS idx_authcodes_expires_at ON authcodes(expires_at);
···11--- Add columns to apps table for pre-registration metadata
22-ALTER TABLE apps ADD COLUMN logo_url TEXT;
33-ALTER TABLE apps ADD COLUMN description TEXT;
44-ALTER TABLE apps ADD COLUMN is_preregistered INTEGER NOT NULL DEFAULT 0;
55-ALTER TABLE apps ADD COLUMN client_secret_hash TEXT;
66--- Add role column to permissions table for per-user, per-app roles
77-ALTER TABLE permissions ADD COLUMN role TEXT;
-48
src/migrations/004_enhance_invites.sql
···11--- Enhance invites table with usage limits, expiry, and app role assignments
22--- Note: SQLite doesn't support DROP COLUMN, so we keep old columns for backward compatibility
33--- But we'll use the new columns going forward
44-55--- Add new columns to invites table
66-ALTER TABLE invites ADD COLUMN max_uses INTEGER DEFAULT 1;
77-ALTER TABLE invites ADD COLUMN current_uses INTEGER NOT NULL DEFAULT 0;
88-ALTER TABLE invites ADD COLUMN expires_at INTEGER;
99-ALTER TABLE invites ADD COLUMN note TEXT;
1010-1111--- Create invite_roles table for app-specific role assignments
1212-CREATE TABLE IF NOT EXISTS invite_roles (
1313- id INTEGER PRIMARY KEY AUTOINCREMENT,
1414- invite_id INTEGER NOT NULL,
1515- app_id INTEGER NOT NULL,
1616- role TEXT NOT NULL,
1717- created_at INTEGER NOT NULL DEFAULT (strftime('%s','now')),
1818- FOREIGN KEY (invite_id) REFERENCES invites(id) ON DELETE CASCADE,
1919- FOREIGN KEY (app_id) REFERENCES apps(id) ON DELETE CASCADE,
2020- UNIQUE(invite_id, app_id)
2121-);
2222-2323-CREATE INDEX IF NOT EXISTS idx_invite_roles_invite_id ON invite_roles(invite_id);
2424-CREATE INDEX IF NOT EXISTS idx_invite_roles_app_id ON invite_roles(app_id);
2525-2626--- Create invite_uses table to track each use (supports multi-use invites)
2727-CREATE TABLE IF NOT EXISTS invite_uses (
2828- id INTEGER PRIMARY KEY AUTOINCREMENT,
2929- invite_id INTEGER NOT NULL,
3030- user_id INTEGER NOT NULL,
3131- used_at INTEGER NOT NULL DEFAULT (strftime('%s','now')),
3232- FOREIGN KEY (invite_id) REFERENCES invites(id) ON DELETE CASCADE,
3333- FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
3434-);
3535-3636-CREATE INDEX IF NOT EXISTS idx_invite_uses_invite_id ON invite_uses(invite_id);
3737-CREATE INDEX IF NOT EXISTS idx_invite_uses_user_id ON invite_uses(user_id);
3838-3939--- Migrate existing single-use invites to new structure
4040--- For invites that have been used, set current_uses = 1 and max_uses = 1
4141-UPDATE invites SET current_uses = 1, max_uses = 1 WHERE used = 1;
4242-4343--- For unused invites, set max_uses = 1 and current_uses = 0
4444-UPDATE invites SET max_uses = 1, current_uses = 0 WHERE used = 0;
4545-4646--- Migrate old invite uses to new invite_uses table
4747-INSERT INTO invite_uses (invite_id, user_id, used_at)
4848-SELECT id, used_by, used_at FROM invites WHERE used = 1 AND used_by IS NOT NULL AND used_at IS NOT NULL;
-5
src/migrations/005_add_app_roles.sql
···11--- Add available_roles column to apps table (JSON array of role names)
22-ALTER TABLE apps ADD COLUMN available_roles TEXT;
33-44--- Add default_role column to apps table
55-ALTER TABLE apps ADD COLUMN default_role TEXT;
-2
src/migrations/006_add_invite_message.sql
···11--- Add public message field to invites
22-ALTER TABLE invites ADD COLUMN message TEXT;