-- ============================================ -- TEMPLATE-SAAS: User Invitations -- Schema: users -- Version: 1.0.0 -- ============================================ CREATE TABLE users.invitations ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES tenants.tenants(id) ON DELETE CASCADE, -- Invitation details email VARCHAR(255) NOT NULL, token VARCHAR(255) UNIQUE NOT NULL, -- Role assignment role_id UUID REFERENCES users.roles(id), -- Status status users.invitation_status DEFAULT 'pending' NOT NULL, -- Expiration expires_at TIMESTAMPTZ NOT NULL, -- Result accepted_at TIMESTAMPTZ, accepted_by_user_id UUID REFERENCES users.users(id), -- Metadata message TEXT, -- Custom message from inviter metadata JSONB DEFAULT '{}'::jsonb, -- Audit created_at TIMESTAMPTZ DEFAULT NOW() NOT NULL, created_by UUID NOT NULL REFERENCES users.users(id), -- Constraints CONSTRAINT valid_invitation_email CHECK (email ~* '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$') ); -- Indexes CREATE INDEX idx_invitations_tenant ON users.invitations(tenant_id); CREATE INDEX idx_invitations_email ON users.invitations(tenant_id, email); CREATE INDEX idx_invitations_token ON users.invitations(token); CREATE INDEX idx_invitations_status ON users.invitations(tenant_id, status) WHERE status = 'pending'; -- RLS ALTER TABLE users.invitations ENABLE ROW LEVEL SECURITY; CREATE POLICY invitations_tenant_isolation ON users.invitations USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID); -- Function to auto-expire invitations CREATE OR REPLACE FUNCTION users.expire_old_invitations() RETURNS void AS $$ BEGIN UPDATE users.invitations SET status = 'expired' WHERE status = 'pending' AND expires_at < NOW(); END; $$ LANGUAGE plpgsql; -- Comments COMMENT ON TABLE users.invitations IS 'User invitations to join tenant'; COMMENT ON COLUMN users.invitations.token IS 'Secure token for invitation link';