template-saas-database-v2/ddl/03-functions.sql
Adrian Flores Cortes 0d3e0228f4 [CRIT-002] feat(ddl): Add public.set_updated_at() function
Create missing trigger function required by MLM schema triggers.
The function updates the updated_at column to CURRENT_TIMESTAMP
on row updates for mlm.structures, mlm.ranks, and mlm.nodes tables.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 17:17:16 -06:00

242 lines
6.8 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
-- ============================================
-- Update updated_at column automatically
CREATE OR REPLACE FUNCTION public.update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- =============================================================================
-- FUNCION: public.set_updated_at()
-- Trigger function para actualizar columna updated_at automaticamente
-- Usada por: mlm.structures, mlm.ranks, mlm.nodes (schema mlm)
-- Nota: Equivalente a update_updated_at_column() - mantener ambas por compatibilidad
-- =============================================================================
CREATE OR REPLACE FUNCTION public.set_updated_at()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = CURRENT_TIMESTAMP;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
COMMENT ON FUNCTION public.set_updated_at() IS 'Trigger function que actualiza automaticamente la columna updated_at al timestamp actual';
-- Create AI configs updated_at trigger (table defined in schemas/ai/)
DO $$
BEGIN
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = 'ai' AND table_name = 'configs') THEN
DROP TRIGGER IF EXISTS update_ai_configs_updated_at ON ai.configs;
CREATE TRIGGER update_ai_configs_updated_at
BEFORE UPDATE ON ai.configs
FOR EACH ROW
EXECUTE FUNCTION update_updated_at_column();
END IF;
END $$;
-- Create webhooks updated_at trigger (table defined in schemas/webhooks/)
DO $$
BEGIN
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = 'webhooks' AND table_name = 'webhooks') THEN
DROP TRIGGER IF EXISTS webhooks_updated_at ON webhooks.webhooks;
CREATE TRIGGER webhooks_updated_at
BEFORE UPDATE ON webhooks.webhooks
FOR EACH ROW
EXECUTE FUNCTION update_updated_at_column();
END IF;
END $$;
-- 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';