Template base para proyectos SaaS multi-tenant. Estructura inicial: - apps/backend (NestJS API) - apps/frontend (React/Vite) - apps/database (PostgreSQL DDL) - docs/ (Documentación) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
66 lines
2.0 KiB
PL/PgSQL
66 lines
2.0 KiB
PL/PgSQL
-- ============================================
|
|
-- 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';
|