1+ BEGIN ;
2+
3+ -- Create api_keys table with encryption for api_key_encrypted (following secrets pattern)
4+ CREATE TABLE IF NOT EXISTS api_keys (
5+ api_key_id UUID NOT NULL ,
6+ developer_id UUID NOT NULL ,
7+ api_key_encrypted BYTEA NOT NULL ,
8+ name TEXT NOT NULL ,
9+ created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP ,
10+ updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP ,
11+ metadata JSONB DEFAULT ' {}' ::jsonb,
12+ deleted_at TIMESTAMPTZ DEFAULT NULL ,
13+ CONSTRAINT pk_api_keys PRIMARY KEY (developer_id, api_key_id),
14+ CONSTRAINT ct_api_keys_metadata_is_object CHECK (jsonb_typeof(metadata) = ' object' ),
15+ CONSTRAINT ct_api_keys_name_valid_identifier CHECK (name ~ ' ^[a-zA-Z][a-zA-Z0-9_]*$' ),
16+ CONSTRAINT fk_api_keys_developer FOREIGN KEY (developer_id) REFERENCES developers(developer_id)
17+ );
18+
19+ -- Create partial unique index instead of constraint with WHERE clause
20+ CREATE UNIQUE INDEX IF NOT EXISTS uq_api_keys_developer_id_name
21+ ON api_keys (developer_id, name)
22+ WHERE deleted_at IS NULL ;
23+
24+ -- Create trigger function for updated_at
25+ CREATE OR REPLACE FUNCTION update_api_keys_timestamp ()
26+ RETURNS TRIGGER AS $$
27+ BEGIN
28+ NEW .updated_at = CURRENT_TIMESTAMP ;
29+ RETURN NEW;
30+ END;
31+ $$ LANGUAGE plpgsql;
32+
33+ -- Create trigger
34+ CREATE TRIGGER update_api_keys_timestamp_trigger
35+ BEFORE UPDATE ON api_keys
36+ FOR EACH ROW
37+ EXECUTE FUNCTION update_api_keys_timestamp();
38+
39+ -- Add indexes
40+ CREATE INDEX IF NOT EXISTS idx_api_keys_developer_id ON api_keys(developer_id);
41+ CREATE INDEX IF NOT EXISTS idx_api_keys_name ON api_keys(name);
42+ CREATE INDEX IF NOT EXISTS idx_api_keys_metadata ON api_keys USING gin(metadata);
43+ CREATE INDEX IF NOT EXISTS idx_api_keys_deleted_at ON api_keys(deleted_at) WHERE deleted_at IS NULL ;
44+
45+ -- Helper functions for encryption/decryption (following secrets pattern)
46+ CREATE OR REPLACE FUNCTION encrypt_api_key (
47+ p_value TEXT ,
48+ p_key TEXT
49+ ) RETURNS BYTEA AS $$
50+ BEGIN
51+ RETURN pgp_sym_encrypt(
52+ p_value,
53+ p_key,
54+ ' cipher-algo=aes256'
55+ );
56+ END;
57+ $$ LANGUAGE plpgsql SECURITY DEFINER;
58+
59+ CREATE OR REPLACE FUNCTION decrypt_api_key (
60+ p_encrypted_value BYTEA ,
61+ p_key TEXT
62+ ) RETURNS TEXT AS $$
63+ BEGIN
64+ RETURN pgp_sym_decrypt(
65+ p_encrypted_value,
66+ p_key
67+ );
68+ END;
69+ $$ LANGUAGE plpgsql SECURITY DEFINER;
70+
71+ -- Add comment to table
72+ COMMENT ON TABLE api_keys IS ' Stores API keys with encryption for developers' ;
73+
74+ COMMIT ;
0 commit comments