- Phoenix API Railing: proxy to PHOENIX_RAILING_URL, tenant me routes - Tenant-auth: X-API-Key support for /api/v1/* (api_keys table) - Migration 026: api_keys table; 025 sovereign stack marketplace - GET /graphql/schema, GET /graphql-playground, api/docs OpenAPI - Integration tests: phoenix-railing.test.ts - docs/api/API_VERSIONING: /api/v1/ railing alignment - docs/phoenix/PORTAL_RAILING_WIRING Made-with: Cursor
46 lines
2.0 KiB
TypeScript
46 lines
2.0 KiB
TypeScript
import { Migration } from '../migrate.js'
|
|
|
|
/**
|
|
* API keys table for client/partner API access (key hash, tenant_id, scopes).
|
|
* Used by X-API-Key auth for /api/v1/* and Phoenix API Railing.
|
|
*/
|
|
export const up: Migration['up'] = async (db) => {
|
|
await db.query(`
|
|
CREATE TABLE IF NOT EXISTS api_keys (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
name VARCHAR(255) NOT NULL,
|
|
key_prefix VARCHAR(20) NOT NULL,
|
|
key_hash VARCHAR(255) NOT NULL UNIQUE,
|
|
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
tenant_id UUID REFERENCES tenants(id) ON DELETE SET NULL,
|
|
permissions JSONB DEFAULT '["read", "write"]'::jsonb,
|
|
last_used_at TIMESTAMP WITH TIME ZONE,
|
|
expires_at TIMESTAMP WITH TIME ZONE,
|
|
revoked BOOLEAN NOT NULL DEFAULT false,
|
|
revoked_at TIMESTAMP WITH TIME ZONE,
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
|
)
|
|
`)
|
|
await db.query(`CREATE INDEX IF NOT EXISTS idx_api_keys_user_id ON api_keys(user_id)`)
|
|
await db.query(`CREATE INDEX IF NOT EXISTS idx_api_keys_tenant_id ON api_keys(tenant_id)`)
|
|
await db.query(`CREATE INDEX IF NOT EXISTS idx_api_keys_key_hash ON api_keys(key_hash)`)
|
|
await db.query(`CREATE INDEX IF NOT EXISTS idx_api_keys_revoked ON api_keys(revoked) WHERE revoked = false`)
|
|
await db.query(`
|
|
DROP TRIGGER IF EXISTS update_api_keys_updated_at ON api_keys
|
|
`)
|
|
await db.query(`
|
|
CREATE TRIGGER update_api_keys_updated_at BEFORE UPDATE ON api_keys
|
|
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column()
|
|
`)
|
|
}
|
|
|
|
export const down: Migration['down'] = async (db) => {
|
|
await db.query(`DROP TRIGGER IF EXISTS update_api_keys_updated_at ON api_keys`)
|
|
await db.query(`DROP INDEX IF EXISTS idx_api_keys_revoked`)
|
|
await db.query(`DROP INDEX IF EXISTS idx_api_keys_key_hash`)
|
|
await db.query(`DROP INDEX IF EXISTS idx_api_keys_tenant_id`)
|
|
await db.query(`DROP INDEX IF EXISTS idx_api_keys_user_id`)
|
|
await db.query(`DROP TABLE IF EXISTS api_keys`)
|
|
}
|