-- Add seat tracking to registration tokens for multi-use support -- This allows tokens to be used multiple times up to a configured limit -- Add seats columns ALTER TABLE registration_tokens ADD COLUMN IF NOT EXISTS max_seats INT NOT NULL DEFAULT 1, ADD COLUMN IF NOT EXISTS seats_used INT NOT NULL DEFAULT 0; -- Backfill existing tokens -- Tokens with status='used' should have seats_used=1, max_seats=1 UPDATE registration_tokens SET seats_used = 1, max_seats = 1 WHERE status = 'used'; -- Active/expired/revoked tokens get max_seats=1, seats_used=0 UPDATE registration_tokens SET seats_used = 0, max_seats = 1 WHERE status IN ('active', 'expired', 'revoked'); -- Add constraint to ensure seats_used doesn't exceed max_seats ALTER TABLE registration_tokens ADD CONSTRAINT chk_seats_used_within_max CHECK (seats_used <= max_seats); -- Add constraint to ensure positive seat values ALTER TABLE registration_tokens ADD CONSTRAINT chk_seats_positive CHECK (max_seats > 0 AND seats_used >= 0); -- Create table to track all agents that used a token (for audit trail) CREATE TABLE IF NOT EXISTS registration_token_usage ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), token_id UUID NOT NULL REFERENCES registration_tokens(id) ON DELETE CASCADE, agent_id UUID NOT NULL REFERENCES agents(id) ON DELETE CASCADE, used_at TIMESTAMP DEFAULT NOW(), UNIQUE(token_id, agent_id) ); CREATE INDEX IF NOT EXISTS idx_token_usage_token_id ON registration_token_usage(token_id); CREATE INDEX IF NOT EXISTS idx_token_usage_agent_id ON registration_token_usage(agent_id); -- Backfill token usage table from existing used_by_agent_id INSERT INTO registration_token_usage (token_id, agent_id, used_at) SELECT id, used_by_agent_id, used_at FROM registration_tokens WHERE used_by_agent_id IS NOT NULL ON CONFLICT (token_id, agent_id) DO NOTHING; -- Update is_registration_token_valid function to check seats CREATE OR REPLACE FUNCTION is_registration_token_valid(token_input VARCHAR) RETURNS BOOLEAN AS $$ DECLARE token_valid BOOLEAN; BEGIN SELECT (status = 'active' AND expires_at > NOW() AND seats_used < max_seats) INTO token_valid FROM registration_tokens WHERE token = token_input; RETURN COALESCE(token_valid, FALSE); END; $$ LANGUAGE plpgsql; -- Update mark_registration_token_used function to increment seats DROP FUNCTION IF EXISTS mark_registration_token_used(VARCHAR, UUID); CREATE FUNCTION mark_registration_token_used(token_input VARCHAR, agent_id_param UUID) RETURNS BOOLEAN AS $$ DECLARE rows_updated INTEGER; -- Fixed: Changed from BOOLEAN to INTEGER to match ROW_COUNT type token_id_val UUID; new_seats_used INT; token_max_seats INT; BEGIN -- Get token ID and current seat info SELECT id, seats_used + 1, max_seats INTO token_id_val, new_seats_used, token_max_seats FROM registration_tokens WHERE token = token_input AND status = 'active' AND expires_at > NOW() AND seats_used < max_seats; -- If no token found or already full, return false IF token_id_val IS NULL THEN RETURN FALSE; END IF; -- Increment seats_used UPDATE registration_tokens SET seats_used = new_seats_used, used_at = CASE WHEN used_at IS NULL THEN NOW() -- First use ELSE used_at -- Keep original first use time END, -- Only mark as 'used' if all seats are now taken status = CASE WHEN new_seats_used >= token_max_seats THEN 'used' ELSE 'active' END WHERE token = token_input AND status = 'active'; GET DIAGNOSTICS rows_updated = ROW_COUNT; -- Record this usage in the audit table IF rows_updated > 0 THEN INSERT INTO registration_token_usage (token_id, agent_id, used_at) VALUES (token_id_val, agent_id_param, NOW()) ON CONFLICT (token_id, agent_id) DO NOTHING; END IF; RETURN rows_updated > 0; END; $$ LANGUAGE plpgsql; -- Add comment for documentation COMMENT ON COLUMN registration_tokens.max_seats IS 'Maximum number of agents that can register with this token'; COMMENT ON COLUMN registration_tokens.seats_used IS 'Number of agents that have registered with this token'; COMMENT ON TABLE registration_token_usage IS 'Audit trail of all agents registered with each token';