template-saas/apps/database/ddl/03-functions.sql
rckrdmrd 26f0e52ca7 feat: Initial commit - template-saas
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>
2026-01-07 04:41:24 -06:00

193 lines
4.9 KiB
PL/PgSQL

-- ============================================
-- TEMPLATE-SAAS: Core Functions
-- Version: 1.0.0
-- ============================================
-- ============================================
-- TENANT CONTEXT FUNCTIONS
-- ============================================
-- Set current tenant for RLS
CREATE OR REPLACE FUNCTION auth.set_current_tenant(p_tenant_id UUID)
RETURNS VOID AS $$
BEGIN
PERFORM set_config('app.current_tenant_id', p_tenant_id::TEXT, FALSE);
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- Get current tenant
CREATE OR REPLACE FUNCTION auth.get_current_tenant()
RETURNS UUID AS $$
BEGIN
RETURN current_setting('app.current_tenant_id', TRUE)::UUID;
EXCEPTION
WHEN OTHERS THEN
RETURN NULL;
END;
$$ LANGUAGE plpgsql STABLE;
-- Set current user
CREATE OR REPLACE FUNCTION auth.set_current_user(p_user_id UUID)
RETURNS VOID AS $$
BEGIN
PERFORM set_config('app.current_user_id', p_user_id::TEXT, FALSE);
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- Get current user
CREATE OR REPLACE FUNCTION auth.get_current_user()
RETURNS UUID AS $$
BEGIN
RETURN current_setting('app.current_user_id', TRUE)::UUID;
EXCEPTION
WHEN OTHERS THEN
RETURN NULL;
END;
$$ LANGUAGE plpgsql STABLE;
-- Clear context
CREATE OR REPLACE FUNCTION auth.clear_context()
RETURNS VOID AS $$
BEGIN
PERFORM set_config('app.current_tenant_id', '', FALSE);
PERFORM set_config('app.current_user_id', '', FALSE);
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- ============================================
-- PLAN & LIMITS FUNCTIONS
-- ============================================
-- Get tenant's current plan limits
CREATE OR REPLACE FUNCTION plans.get_tenant_limits(p_tenant_id UUID)
RETURNS JSONB AS $$
DECLARE
v_limits JSONB;
BEGIN
SELECT p.limits INTO v_limits
FROM tenants.tenants t
JOIN plans.plans p ON t.plan_id = p.id
WHERE t.id = p_tenant_id;
RETURN COALESCE(v_limits, '{}'::jsonb);
END;
$$ LANGUAGE plpgsql STABLE;
-- Check if tenant can perform action based on limits
CREATE OR REPLACE FUNCTION plans.check_limit(
p_tenant_id UUID,
p_limit_key VARCHAR,
p_current_count INT
)
RETURNS BOOLEAN AS $$
DECLARE
v_limit INT;
BEGIN
SELECT (plans.get_tenant_limits(p_tenant_id)->>p_limit_key)::INT INTO v_limit;
IF v_limit IS NULL THEN
RETURN TRUE; -- No limit defined
END IF;
RETURN p_current_count < v_limit;
END;
$$ LANGUAGE plpgsql STABLE;
-- Get tenant's feature flags
CREATE OR REPLACE FUNCTION plans.get_tenant_features(p_tenant_id UUID)
RETURNS JSONB AS $$
DECLARE
v_features JSONB;
BEGIN
SELECT p.included_features INTO v_features
FROM tenants.tenants t
JOIN plans.plans p ON t.plan_id = p.id
WHERE t.id = p_tenant_id;
RETURN COALESCE(v_features, '[]'::jsonb);
END;
$$ LANGUAGE plpgsql STABLE;
-- Check if tenant has feature
CREATE OR REPLACE FUNCTION plans.has_feature(
p_tenant_id UUID,
p_feature_code VARCHAR
)
RETURNS BOOLEAN AS $$
BEGIN
RETURN plans.get_tenant_features(p_tenant_id) ? p_feature_code;
END;
$$ LANGUAGE plpgsql STABLE;
-- ============================================
-- USER COUNT FUNCTIONS
-- ============================================
-- Count active users in tenant
CREATE OR REPLACE FUNCTION users.count_active_users(p_tenant_id UUID)
RETURNS INT AS $$
BEGIN
RETURN (
SELECT COUNT(*)
FROM users.users
WHERE tenant_id = p_tenant_id
AND status = 'active'
AND deleted_at IS NULL
);
END;
$$ LANGUAGE plpgsql STABLE;
-- Check if tenant can add more users
CREATE OR REPLACE FUNCTION users.can_add_user(p_tenant_id UUID)
RETURNS BOOLEAN AS $$
BEGIN
RETURN plans.check_limit(
p_tenant_id,
'max_users',
users.count_active_users(p_tenant_id)
);
END;
$$ LANGUAGE plpgsql STABLE;
-- ============================================
-- UTILITY FUNCTIONS
-- ============================================
-- Generate slug from name
CREATE OR REPLACE FUNCTION public.slugify(p_text VARCHAR)
RETURNS VARCHAR AS $$
BEGIN
RETURN LOWER(
REGEXP_REPLACE(
REGEXP_REPLACE(
TRIM(p_text),
'[^a-zA-Z0-9\s-]', '', 'g'
),
'\s+', '-', 'g'
)
);
END;
$$ LANGUAGE plpgsql IMMUTABLE;
-- Generate random token
CREATE OR REPLACE FUNCTION public.generate_token(p_length INT DEFAULT 32)
RETURNS VARCHAR AS $$
BEGIN
RETURN encode(gen_random_bytes(p_length), 'hex');
END;
$$ LANGUAGE plpgsql;
-- Hash token
CREATE OR REPLACE FUNCTION public.hash_token(p_token VARCHAR)
RETURNS VARCHAR AS $$
BEGIN
RETURN encode(digest(p_token, 'sha256'), 'hex');
END;
$$ LANGUAGE plpgsql IMMUTABLE;
-- Comments
COMMENT ON FUNCTION auth.set_current_tenant IS 'Set tenant context for RLS policies';
COMMENT ON FUNCTION auth.get_current_tenant IS 'Get current tenant from context';
COMMENT ON FUNCTION plans.check_limit IS 'Check if action is within plan limits';
COMMENT ON FUNCTION plans.has_feature IS 'Check if tenant has access to feature';