aios-core 2.2.2 → 2.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.aios-core/.session/current-session.json +14 -14
- package/.aios-core/cli/commands/migrate/validate.js +1 -1
- package/.aios-core/core/docs/session-update-pattern.md +17 -10
- package/.aios-core/core/elicitation/elicitation-engine.js +11 -6
- package/.aios-core/core/elicitation/session-manager.js +2 -1
- package/.aios-core/core/registry/registry-schema.json +166 -166
- package/.aios-core/core/registry/service-registry.json +6585 -6585
- package/.aios-core/core-config.yaml +12 -1
- package/.aios-core/data/agent-config-requirements.yaml +5 -5
- package/.aios-core/development/agents/devops.md +12 -0
- package/.aios-core/development/scripts/squad/README.md +112 -0
- package/.aios-core/development/scripts/squad/index.js +41 -0
- package/.aios-core/development/scripts/squad/squad-loader.js +359 -0
- package/.aios-core/development/scripts/squad/squad-validator.js +685 -0
- package/.aios-core/development/tasks/add-mcp.md +11 -5
- package/.aios-core/development/tasks/search-mcp.md +309 -0
- package/.aios-core/development/tasks/setup-mcp-docker.md +11 -8
- package/.aios-core/development/tasks/squad-creator-validate.md +151 -0
- package/.aios-core/docs/standards/AGENT-PERSONALIZATION-STANDARD-V1.md +3 -3
- package/.aios-core/index.d.ts +7 -7
- package/.aios-core/index.js +1 -1
- package/.aios-core/infrastructure/scripts/batch-creator.js +1 -1
- package/.aios-core/infrastructure/scripts/component-generator.js +1 -1
- package/.aios-core/infrastructure/templates/coderabbit.yaml.template +279 -279
- package/.aios-core/infrastructure/templates/github-workflows/ci.yml.template +169 -169
- package/.aios-core/infrastructure/templates/github-workflows/pr-automation.yml.template +330 -330
- package/.aios-core/infrastructure/templates/github-workflows/release.yml.template +196 -196
- package/.aios-core/infrastructure/templates/gitignore/gitignore-aios-base.tmpl +63 -63
- package/.aios-core/infrastructure/templates/gitignore/gitignore-brownfield-merge.tmpl +18 -18
- package/.aios-core/infrastructure/templates/gitignore/gitignore-node.tmpl +85 -85
- package/.aios-core/infrastructure/templates/gitignore/gitignore-python.tmpl +145 -145
- package/.aios-core/infrastructure/tests/utilities-audit-results.json +500 -500
- package/.aios-core/infrastructure/tools/README.md +1 -1
- package/.aios-core/install-manifest.yaml +4 -1
- package/.aios-core/manifests/schema/manifest-schema.json +190 -190
- package/.aios-core/manifests/workers.csv +203 -203
- package/.aios-core/package.json +102 -102
- package/.aios-core/product/templates/activation-instructions-template.md +7 -7
- package/.aios-core/product/templates/adr.hbs +125 -125
- package/.aios-core/product/templates/component-react-tmpl.tsx +98 -98
- package/.aios-core/product/templates/dbdr.hbs +241 -241
- package/.aios-core/product/templates/engine/schemas/adr.schema.json +102 -102
- package/.aios-core/product/templates/engine/schemas/dbdr.schema.json +205 -205
- package/.aios-core/product/templates/engine/schemas/epic.schema.json +175 -175
- package/.aios-core/product/templates/engine/schemas/pmdr.schema.json +175 -175
- package/.aios-core/product/templates/engine/schemas/prd-v2.schema.json +300 -300
- package/.aios-core/product/templates/engine/schemas/prd.schema.json +152 -152
- package/.aios-core/product/templates/engine/schemas/story.schema.json +222 -222
- package/.aios-core/product/templates/engine/schemas/task.schema.json +154 -154
- package/.aios-core/product/templates/epic.hbs +212 -212
- package/.aios-core/product/templates/eslintrc-security.json +32 -32
- package/.aios-core/product/templates/github-actions-cd.yml +212 -212
- package/.aios-core/product/templates/github-actions-ci.yml +172 -172
- package/.aios-core/product/templates/pmdr.hbs +186 -186
- package/.aios-core/product/templates/prd-v2.0.hbs +216 -216
- package/.aios-core/product/templates/prd.hbs +201 -201
- package/.aios-core/product/templates/shock-report-tmpl.html +502 -502
- package/.aios-core/product/templates/story.hbs +263 -263
- package/.aios-core/product/templates/task.hbs +170 -170
- package/.aios-core/product/templates/tmpl-comment-on-examples.sql +158 -158
- package/.aios-core/product/templates/tmpl-migration-script.sql +91 -91
- package/.aios-core/product/templates/tmpl-rls-granular-policies.sql +104 -104
- package/.aios-core/product/templates/tmpl-rls-kiss-policy.sql +10 -10
- package/.aios-core/product/templates/tmpl-rls-roles.sql +135 -135
- package/.aios-core/product/templates/tmpl-rls-simple.sql +77 -77
- package/.aios-core/product/templates/tmpl-rls-tenant.sql +152 -152
- package/.aios-core/product/templates/tmpl-rollback-script.sql +77 -77
- package/.aios-core/product/templates/tmpl-seed-data.sql +140 -140
- package/.aios-core/product/templates/tmpl-smoke-test.sql +16 -16
- package/.aios-core/product/templates/tmpl-staging-copy-merge.sql +139 -139
- package/.aios-core/product/templates/tmpl-stored-proc.sql +140 -140
- package/.aios-core/product/templates/tmpl-trigger.sql +152 -152
- package/.aios-core/product/templates/tmpl-view-materialized.sql +133 -133
- package/.aios-core/product/templates/tmpl-view.sql +177 -177
- package/.aios-core/product/templates/token-exports-css-tmpl.css +240 -240
- package/.aios-core/quality/schemas/quality-metrics.schema.json +233 -233
- package/.aios-core/schemas/squad-schema.json +185 -0
- package/.aios-core/scripts/README.md +90 -322
- package/.aios-core/scripts/migrate-framework-docs.sh +300 -300
- package/.claude/rules/mcp-usage.md +116 -100
- package/LICENSE +48 -48
- package/README.md +3 -4
- package/bin/aios.js +2 -1
- package/package.json +1 -3
- package/packages/installer/package.json +39 -39
- package/templates/squad/LICENSE +21 -21
- package/templates/squad/README.md +37 -37
- package/templates/squad/agents/example-agent.yaml +36 -36
- package/templates/squad/package.json +19 -19
- package/templates/squad/squad.yaml +25 -25
- package/templates/squad/tasks/example-task.yaml +46 -46
- package/templates/squad/templates/example-template.md +24 -24
- package/templates/squad/tests/example-agent.test.js +53 -53
- package/templates/squad/workflows/example-workflow.yaml +54 -54
- package/tools/diagnose-npx-issue.ps1 +96 -96
- package/tools/quick-diagnose.cmd +85 -85
- package/tools/quick-diagnose.ps1 +117 -117
- package/.aios-core/core/data/agent-config-requirements.yaml +0 -368
- package/.aios-core/core/data/aios-kb.md +0 -924
- package/.aios-core/core/data/workflow-patterns.yaml +0 -267
- package/.aios-core/product/templates/1mcp-config.yaml +0 -225
- package/.aios-core/scripts/context-detector.js +0 -226
- package/.aios-core/scripts/elicitation-engine.js +0 -385
- package/.aios-core/scripts/elicitation-session-manager.js +0 -300
- package/.claude/CLAUDE.md +0 -221
|
@@ -1,140 +1,140 @@
|
|
|
1
|
-
-- Stored Procedure Template
|
|
2
|
-
-- Function: :function_name
|
|
3
|
-
-- Created: :created_date
|
|
4
|
-
-- Author: :author
|
|
5
|
-
-- Description: :description
|
|
6
|
-
--
|
|
7
|
-
-- IMPORTANT: Consider security implications of SECURITY DEFINER vs SECURITY INVOKER
|
|
8
|
-
|
|
9
|
-
-- =============================================================================
|
|
10
|
-
-- BASIC FUNCTION TEMPLATE
|
|
11
|
-
-- =============================================================================
|
|
12
|
-
|
|
13
|
-
CREATE OR REPLACE FUNCTION :function_name(
|
|
14
|
-
-- Input parameters
|
|
15
|
-
p_param1 :param1_type,
|
|
16
|
-
p_param2 :param2_type DEFAULT NULL
|
|
17
|
-
)
|
|
18
|
-
RETURNS :return_type
|
|
19
|
-
LANGUAGE plpgsql
|
|
20
|
-
-- SECURITY DEFINER: Runs with function owner's privileges (use carefully)
|
|
21
|
-
-- SECURITY INVOKER: Runs with caller's privileges (default, safer)
|
|
22
|
-
SECURITY INVOKER
|
|
23
|
-
-- STABLE: Doesn't modify database, same result for same inputs within transaction
|
|
24
|
-
-- VOLATILE: May modify database or return different results (default)
|
|
25
|
-
-- IMMUTABLE: Always returns same result for same inputs (for indexing)
|
|
26
|
-
STABLE
|
|
27
|
-
AS $$
|
|
28
|
-
DECLARE
|
|
29
|
-
v_result :return_type;
|
|
30
|
-
v_variable :variable_type;
|
|
31
|
-
BEGIN
|
|
32
|
-
-- Input validation
|
|
33
|
-
IF p_param1 IS NULL THEN
|
|
34
|
-
RAISE EXCEPTION 'p_param1 cannot be null';
|
|
35
|
-
END IF;
|
|
36
|
-
|
|
37
|
-
-- Main logic
|
|
38
|
-
SELECT column_name
|
|
39
|
-
INTO v_result
|
|
40
|
-
FROM :table_name
|
|
41
|
-
WHERE id = p_param1;
|
|
42
|
-
|
|
43
|
-
-- Return result
|
|
44
|
-
RETURN v_result;
|
|
45
|
-
|
|
46
|
-
EXCEPTION
|
|
47
|
-
WHEN no_data_found THEN
|
|
48
|
-
RAISE EXCEPTION 'No data found for id: %', p_param1;
|
|
49
|
-
WHEN OTHERS THEN
|
|
50
|
-
RAISE EXCEPTION 'Error in :function_name: %', SQLERRM;
|
|
51
|
-
END;
|
|
52
|
-
$$;
|
|
53
|
-
|
|
54
|
-
-- Grant execute permission
|
|
55
|
-
GRANT EXECUTE ON FUNCTION :function_name(:param1_type, :param2_type) TO authenticated;
|
|
56
|
-
|
|
57
|
-
-- Add function comment
|
|
58
|
-
COMMENT ON FUNCTION :function_name IS ':description';
|
|
59
|
-
|
|
60
|
-
-- =============================================================================
|
|
61
|
-
-- FUNCTION RETURNING TABLE (for complex queries)
|
|
62
|
-
-- =============================================================================
|
|
63
|
-
|
|
64
|
-
CREATE OR REPLACE FUNCTION :function_name_table(
|
|
65
|
-
p_filter_param :filter_type DEFAULT NULL
|
|
66
|
-
)
|
|
67
|
-
RETURNS TABLE (
|
|
68
|
-
id UUID,
|
|
69
|
-
name TEXT,
|
|
70
|
-
created_at TIMESTAMPTZ
|
|
71
|
-
)
|
|
72
|
-
LANGUAGE plpgsql
|
|
73
|
-
SECURITY INVOKER
|
|
74
|
-
STABLE
|
|
75
|
-
AS $$
|
|
76
|
-
BEGIN
|
|
77
|
-
RETURN QUERY
|
|
78
|
-
SELECT
|
|
79
|
-
t.id,
|
|
80
|
-
t.name,
|
|
81
|
-
t.created_at
|
|
82
|
-
FROM :table_name t
|
|
83
|
-
WHERE
|
|
84
|
-
(p_filter_param IS NULL OR t.filter_column = p_filter_param)
|
|
85
|
-
AND t.user_id = auth.uid() -- RLS-aware
|
|
86
|
-
ORDER BY t.created_at DESC;
|
|
87
|
-
END;
|
|
88
|
-
$$;
|
|
89
|
-
|
|
90
|
-
-- =============================================================================
|
|
91
|
-
-- FUNCTION WITH TRANSACTION (for mutations)
|
|
92
|
-
-- =============================================================================
|
|
93
|
-
|
|
94
|
-
CREATE OR REPLACE FUNCTION :function_name_mutation(
|
|
95
|
-
p_input_data JSONB
|
|
96
|
-
)
|
|
97
|
-
RETURNS UUID
|
|
98
|
-
LANGUAGE plpgsql
|
|
99
|
-
SECURITY INVOKER
|
|
100
|
-
VOLATILE
|
|
101
|
-
AS $$
|
|
102
|
-
DECLARE
|
|
103
|
-
v_new_id UUID;
|
|
104
|
-
BEGIN
|
|
105
|
-
-- Validate input
|
|
106
|
-
IF NOT (p_input_data ? 'required_field') THEN
|
|
107
|
-
RAISE EXCEPTION 'required_field is missing from input';
|
|
108
|
-
END IF;
|
|
109
|
-
|
|
110
|
-
-- Insert new record
|
|
111
|
-
INSERT INTO :table_name (
|
|
112
|
-
name,
|
|
113
|
-
data,
|
|
114
|
-
user_id,
|
|
115
|
-
created_at
|
|
116
|
-
) VALUES (
|
|
117
|
-
p_input_data->>'name',
|
|
118
|
-
p_input_data->'data',
|
|
119
|
-
auth.uid(),
|
|
120
|
-
NOW()
|
|
121
|
-
)
|
|
122
|
-
RETURNING id INTO v_new_id;
|
|
123
|
-
|
|
124
|
-
-- Audit log (optional)
|
|
125
|
-
INSERT INTO audit_log (action, table_name, record_id, user_id)
|
|
126
|
-
VALUES ('INSERT', ':table_name', v_new_id, auth.uid());
|
|
127
|
-
|
|
128
|
-
RETURN v_new_id;
|
|
129
|
-
END;
|
|
130
|
-
$$;
|
|
131
|
-
|
|
132
|
-
-- =============================================================================
|
|
133
|
-
-- RPC ENDPOINT (Supabase-style)
|
|
134
|
-
-- =============================================================================
|
|
135
|
-
-- Call from client: supabase.rpc(':function_name', { p_param1: value })
|
|
136
|
-
--
|
|
137
|
-
-- For Supabase, ensure:
|
|
138
|
-
-- 1. Function is in public schema
|
|
139
|
-
-- 2. GRANT EXECUTE to appropriate roles
|
|
140
|
-
-- 3. Consider using SECURITY DEFINER for bypassing RLS when needed
|
|
1
|
+
-- Stored Procedure Template
|
|
2
|
+
-- Function: :function_name
|
|
3
|
+
-- Created: :created_date
|
|
4
|
+
-- Author: :author
|
|
5
|
+
-- Description: :description
|
|
6
|
+
--
|
|
7
|
+
-- IMPORTANT: Consider security implications of SECURITY DEFINER vs SECURITY INVOKER
|
|
8
|
+
|
|
9
|
+
-- =============================================================================
|
|
10
|
+
-- BASIC FUNCTION TEMPLATE
|
|
11
|
+
-- =============================================================================
|
|
12
|
+
|
|
13
|
+
CREATE OR REPLACE FUNCTION :function_name(
|
|
14
|
+
-- Input parameters
|
|
15
|
+
p_param1 :param1_type,
|
|
16
|
+
p_param2 :param2_type DEFAULT NULL
|
|
17
|
+
)
|
|
18
|
+
RETURNS :return_type
|
|
19
|
+
LANGUAGE plpgsql
|
|
20
|
+
-- SECURITY DEFINER: Runs with function owner's privileges (use carefully)
|
|
21
|
+
-- SECURITY INVOKER: Runs with caller's privileges (default, safer)
|
|
22
|
+
SECURITY INVOKER
|
|
23
|
+
-- STABLE: Doesn't modify database, same result for same inputs within transaction
|
|
24
|
+
-- VOLATILE: May modify database or return different results (default)
|
|
25
|
+
-- IMMUTABLE: Always returns same result for same inputs (for indexing)
|
|
26
|
+
STABLE
|
|
27
|
+
AS $$
|
|
28
|
+
DECLARE
|
|
29
|
+
v_result :return_type;
|
|
30
|
+
v_variable :variable_type;
|
|
31
|
+
BEGIN
|
|
32
|
+
-- Input validation
|
|
33
|
+
IF p_param1 IS NULL THEN
|
|
34
|
+
RAISE EXCEPTION 'p_param1 cannot be null';
|
|
35
|
+
END IF;
|
|
36
|
+
|
|
37
|
+
-- Main logic
|
|
38
|
+
SELECT column_name
|
|
39
|
+
INTO v_result
|
|
40
|
+
FROM :table_name
|
|
41
|
+
WHERE id = p_param1;
|
|
42
|
+
|
|
43
|
+
-- Return result
|
|
44
|
+
RETURN v_result;
|
|
45
|
+
|
|
46
|
+
EXCEPTION
|
|
47
|
+
WHEN no_data_found THEN
|
|
48
|
+
RAISE EXCEPTION 'No data found for id: %', p_param1;
|
|
49
|
+
WHEN OTHERS THEN
|
|
50
|
+
RAISE EXCEPTION 'Error in :function_name: %', SQLERRM;
|
|
51
|
+
END;
|
|
52
|
+
$$;
|
|
53
|
+
|
|
54
|
+
-- Grant execute permission
|
|
55
|
+
GRANT EXECUTE ON FUNCTION :function_name(:param1_type, :param2_type) TO authenticated;
|
|
56
|
+
|
|
57
|
+
-- Add function comment
|
|
58
|
+
COMMENT ON FUNCTION :function_name IS ':description';
|
|
59
|
+
|
|
60
|
+
-- =============================================================================
|
|
61
|
+
-- FUNCTION RETURNING TABLE (for complex queries)
|
|
62
|
+
-- =============================================================================
|
|
63
|
+
|
|
64
|
+
CREATE OR REPLACE FUNCTION :function_name_table(
|
|
65
|
+
p_filter_param :filter_type DEFAULT NULL
|
|
66
|
+
)
|
|
67
|
+
RETURNS TABLE (
|
|
68
|
+
id UUID,
|
|
69
|
+
name TEXT,
|
|
70
|
+
created_at TIMESTAMPTZ
|
|
71
|
+
)
|
|
72
|
+
LANGUAGE plpgsql
|
|
73
|
+
SECURITY INVOKER
|
|
74
|
+
STABLE
|
|
75
|
+
AS $$
|
|
76
|
+
BEGIN
|
|
77
|
+
RETURN QUERY
|
|
78
|
+
SELECT
|
|
79
|
+
t.id,
|
|
80
|
+
t.name,
|
|
81
|
+
t.created_at
|
|
82
|
+
FROM :table_name t
|
|
83
|
+
WHERE
|
|
84
|
+
(p_filter_param IS NULL OR t.filter_column = p_filter_param)
|
|
85
|
+
AND t.user_id = auth.uid() -- RLS-aware
|
|
86
|
+
ORDER BY t.created_at DESC;
|
|
87
|
+
END;
|
|
88
|
+
$$;
|
|
89
|
+
|
|
90
|
+
-- =============================================================================
|
|
91
|
+
-- FUNCTION WITH TRANSACTION (for mutations)
|
|
92
|
+
-- =============================================================================
|
|
93
|
+
|
|
94
|
+
CREATE OR REPLACE FUNCTION :function_name_mutation(
|
|
95
|
+
p_input_data JSONB
|
|
96
|
+
)
|
|
97
|
+
RETURNS UUID
|
|
98
|
+
LANGUAGE plpgsql
|
|
99
|
+
SECURITY INVOKER
|
|
100
|
+
VOLATILE
|
|
101
|
+
AS $$
|
|
102
|
+
DECLARE
|
|
103
|
+
v_new_id UUID;
|
|
104
|
+
BEGIN
|
|
105
|
+
-- Validate input
|
|
106
|
+
IF NOT (p_input_data ? 'required_field') THEN
|
|
107
|
+
RAISE EXCEPTION 'required_field is missing from input';
|
|
108
|
+
END IF;
|
|
109
|
+
|
|
110
|
+
-- Insert new record
|
|
111
|
+
INSERT INTO :table_name (
|
|
112
|
+
name,
|
|
113
|
+
data,
|
|
114
|
+
user_id,
|
|
115
|
+
created_at
|
|
116
|
+
) VALUES (
|
|
117
|
+
p_input_data->>'name',
|
|
118
|
+
p_input_data->'data',
|
|
119
|
+
auth.uid(),
|
|
120
|
+
NOW()
|
|
121
|
+
)
|
|
122
|
+
RETURNING id INTO v_new_id;
|
|
123
|
+
|
|
124
|
+
-- Audit log (optional)
|
|
125
|
+
INSERT INTO audit_log (action, table_name, record_id, user_id)
|
|
126
|
+
VALUES ('INSERT', ':table_name', v_new_id, auth.uid());
|
|
127
|
+
|
|
128
|
+
RETURN v_new_id;
|
|
129
|
+
END;
|
|
130
|
+
$$;
|
|
131
|
+
|
|
132
|
+
-- =============================================================================
|
|
133
|
+
-- RPC ENDPOINT (Supabase-style)
|
|
134
|
+
-- =============================================================================
|
|
135
|
+
-- Call from client: supabase.rpc(':function_name', { p_param1: value })
|
|
136
|
+
--
|
|
137
|
+
-- For Supabase, ensure:
|
|
138
|
+
-- 1. Function is in public schema
|
|
139
|
+
-- 2. GRANT EXECUTE to appropriate roles
|
|
140
|
+
-- 3. Consider using SECURITY DEFINER for bypassing RLS when needed
|
|
@@ -1,152 +1,152 @@
|
|
|
1
|
-
-- Trigger Template
|
|
2
|
-
-- Table: :table_name
|
|
3
|
-
-- Created: :created_date
|
|
4
|
-
-- Author: :author
|
|
5
|
-
-- Description: :description
|
|
6
|
-
--
|
|
7
|
-
-- IMPORTANT: Triggers run automatically - test thoroughly before deployment
|
|
8
|
-
|
|
9
|
-
-- =============================================================================
|
|
10
|
-
-- UPDATED_AT TRIGGER (Most Common)
|
|
11
|
-
-- =============================================================================
|
|
12
|
-
|
|
13
|
-
-- Trigger function: Updates updated_at column on row modification
|
|
14
|
-
CREATE OR REPLACE FUNCTION update_updated_at_column()
|
|
15
|
-
RETURNS TRIGGER AS $$
|
|
16
|
-
BEGIN
|
|
17
|
-
NEW.updated_at = NOW();
|
|
18
|
-
RETURN NEW;
|
|
19
|
-
END;
|
|
20
|
-
$$ LANGUAGE plpgsql;
|
|
21
|
-
|
|
22
|
-
-- Apply to table
|
|
23
|
-
DROP TRIGGER IF EXISTS trigger_:table_name_updated_at ON :table_name;
|
|
24
|
-
CREATE TRIGGER trigger_:table_name_updated_at
|
|
25
|
-
BEFORE UPDATE ON :table_name
|
|
26
|
-
FOR EACH ROW
|
|
27
|
-
EXECUTE FUNCTION update_updated_at_column();
|
|
28
|
-
|
|
29
|
-
-- =============================================================================
|
|
30
|
-
-- AUDIT LOG TRIGGER
|
|
31
|
-
-- =============================================================================
|
|
32
|
-
|
|
33
|
-
-- Audit log table (if not exists)
|
|
34
|
-
CREATE TABLE IF NOT EXISTS audit_log (
|
|
35
|
-
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
36
|
-
table_name TEXT NOT NULL,
|
|
37
|
-
record_id UUID NOT NULL,
|
|
38
|
-
action TEXT NOT NULL CHECK (action IN ('INSERT', 'UPDATE', 'DELETE')),
|
|
39
|
-
old_data JSONB,
|
|
40
|
-
new_data JSONB,
|
|
41
|
-
changed_by UUID REFERENCES auth.users(id),
|
|
42
|
-
changed_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
43
|
-
);
|
|
44
|
-
|
|
45
|
-
CREATE INDEX IF NOT EXISTS idx_audit_log_table_record ON audit_log(table_name, record_id);
|
|
46
|
-
CREATE INDEX IF NOT EXISTS idx_audit_log_changed_at ON audit_log(changed_at);
|
|
47
|
-
|
|
48
|
-
-- Audit trigger function
|
|
49
|
-
CREATE OR REPLACE FUNCTION audit_trigger_function()
|
|
50
|
-
RETURNS TRIGGER AS $$
|
|
51
|
-
BEGIN
|
|
52
|
-
IF TG_OP = 'INSERT' THEN
|
|
53
|
-
INSERT INTO audit_log (table_name, record_id, action, new_data, changed_by)
|
|
54
|
-
VALUES (TG_TABLE_NAME, NEW.id, 'INSERT', to_jsonb(NEW), auth.uid());
|
|
55
|
-
RETURN NEW;
|
|
56
|
-
ELSIF TG_OP = 'UPDATE' THEN
|
|
57
|
-
INSERT INTO audit_log (table_name, record_id, action, old_data, new_data, changed_by)
|
|
58
|
-
VALUES (TG_TABLE_NAME, NEW.id, 'UPDATE', to_jsonb(OLD), to_jsonb(NEW), auth.uid());
|
|
59
|
-
RETURN NEW;
|
|
60
|
-
ELSIF TG_OP = 'DELETE' THEN
|
|
61
|
-
INSERT INTO audit_log (table_name, record_id, action, old_data, changed_by)
|
|
62
|
-
VALUES (TG_TABLE_NAME, OLD.id, 'DELETE', to_jsonb(OLD), auth.uid());
|
|
63
|
-
RETURN OLD;
|
|
64
|
-
END IF;
|
|
65
|
-
RETURN NULL;
|
|
66
|
-
END;
|
|
67
|
-
$$ LANGUAGE plpgsql SECURITY DEFINER;
|
|
68
|
-
|
|
69
|
-
-- Apply audit trigger to table
|
|
70
|
-
DROP TRIGGER IF EXISTS trigger_:table_name_audit ON :table_name;
|
|
71
|
-
CREATE TRIGGER trigger_:table_name_audit
|
|
72
|
-
AFTER INSERT OR UPDATE OR DELETE ON :table_name
|
|
73
|
-
FOR EACH ROW
|
|
74
|
-
EXECUTE FUNCTION audit_trigger_function();
|
|
75
|
-
|
|
76
|
-
-- =============================================================================
|
|
77
|
-
-- VALIDATION TRIGGER
|
|
78
|
-
-- =============================================================================
|
|
79
|
-
|
|
80
|
-
-- Custom validation trigger function
|
|
81
|
-
CREATE OR REPLACE FUNCTION :table_name_validation_trigger()
|
|
82
|
-
RETURNS TRIGGER AS $$
|
|
83
|
-
BEGIN
|
|
84
|
-
-- Example: Validate email format
|
|
85
|
-
IF NEW.email IS NOT NULL AND NEW.email !~ '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$' THEN
|
|
86
|
-
RAISE EXCEPTION 'Invalid email format: %', NEW.email;
|
|
87
|
-
END IF;
|
|
88
|
-
|
|
89
|
-
-- Example: Validate status transitions
|
|
90
|
-
IF TG_OP = 'UPDATE' AND OLD.status = 'completed' AND NEW.status != 'completed' THEN
|
|
91
|
-
RAISE EXCEPTION 'Cannot change status from completed';
|
|
92
|
-
END IF;
|
|
93
|
-
|
|
94
|
-
-- Example: Auto-set values
|
|
95
|
-
IF TG_OP = 'INSERT' THEN
|
|
96
|
-
NEW.created_by = COALESCE(NEW.created_by, auth.uid());
|
|
97
|
-
END IF;
|
|
98
|
-
|
|
99
|
-
RETURN NEW;
|
|
100
|
-
END;
|
|
101
|
-
$$ LANGUAGE plpgsql;
|
|
102
|
-
|
|
103
|
-
DROP TRIGGER IF EXISTS trigger_:table_name_validation ON :table_name;
|
|
104
|
-
CREATE TRIGGER trigger_:table_name_validation
|
|
105
|
-
BEFORE INSERT OR UPDATE ON :table_name
|
|
106
|
-
FOR EACH ROW
|
|
107
|
-
EXECUTE FUNCTION :table_name_validation_trigger();
|
|
108
|
-
|
|
109
|
-
-- =============================================================================
|
|
110
|
-
-- SOFT DELETE TRIGGER
|
|
111
|
-
-- =============================================================================
|
|
112
|
-
|
|
113
|
-
-- Instead of deleting, set deleted_at timestamp
|
|
114
|
-
CREATE OR REPLACE FUNCTION soft_delete_trigger()
|
|
115
|
-
RETURNS TRIGGER AS $$
|
|
116
|
-
BEGIN
|
|
117
|
-
-- Instead of DELETE, UPDATE with deleted_at
|
|
118
|
-
UPDATE :table_name
|
|
119
|
-
SET deleted_at = NOW()
|
|
120
|
-
WHERE id = OLD.id;
|
|
121
|
-
|
|
122
|
-
-- Return NULL to cancel the actual DELETE
|
|
123
|
-
RETURN NULL;
|
|
124
|
-
END;
|
|
125
|
-
$$ LANGUAGE plpgsql;
|
|
126
|
-
|
|
127
|
-
DROP TRIGGER IF EXISTS trigger_:table_name_soft_delete ON :table_name;
|
|
128
|
-
CREATE TRIGGER trigger_:table_name_soft_delete
|
|
129
|
-
BEFORE DELETE ON :table_name
|
|
130
|
-
FOR EACH ROW
|
|
131
|
-
EXECUTE FUNCTION soft_delete_trigger();
|
|
132
|
-
|
|
133
|
-
-- =============================================================================
|
|
134
|
-
-- NOTIFICATION TRIGGER (for Supabase Realtime)
|
|
135
|
-
-- =============================================================================
|
|
136
|
-
|
|
137
|
-
-- Notify on changes (Supabase handles this automatically for enabled tables)
|
|
138
|
-
-- Manual implementation if needed:
|
|
139
|
-
|
|
140
|
-
CREATE OR REPLACE FUNCTION notify_changes()
|
|
141
|
-
RETURNS TRIGGER AS $$
|
|
142
|
-
BEGIN
|
|
143
|
-
PERFORM pg_notify(
|
|
144
|
-
TG_TABLE_NAME || '_changes',
|
|
145
|
-
json_build_object(
|
|
146
|
-
'operation', TG_OP,
|
|
147
|
-
'record', CASE WHEN TG_OP = 'DELETE' THEN to_jsonb(OLD) ELSE to_jsonb(NEW) END
|
|
148
|
-
)::TEXT
|
|
149
|
-
);
|
|
150
|
-
RETURN COALESCE(NEW, OLD);
|
|
151
|
-
END;
|
|
152
|
-
$$ LANGUAGE plpgsql;
|
|
1
|
+
-- Trigger Template
|
|
2
|
+
-- Table: :table_name
|
|
3
|
+
-- Created: :created_date
|
|
4
|
+
-- Author: :author
|
|
5
|
+
-- Description: :description
|
|
6
|
+
--
|
|
7
|
+
-- IMPORTANT: Triggers run automatically - test thoroughly before deployment
|
|
8
|
+
|
|
9
|
+
-- =============================================================================
|
|
10
|
+
-- UPDATED_AT TRIGGER (Most Common)
|
|
11
|
+
-- =============================================================================
|
|
12
|
+
|
|
13
|
+
-- Trigger function: Updates updated_at column on row modification
|
|
14
|
+
CREATE OR REPLACE FUNCTION update_updated_at_column()
|
|
15
|
+
RETURNS TRIGGER AS $$
|
|
16
|
+
BEGIN
|
|
17
|
+
NEW.updated_at = NOW();
|
|
18
|
+
RETURN NEW;
|
|
19
|
+
END;
|
|
20
|
+
$$ LANGUAGE plpgsql;
|
|
21
|
+
|
|
22
|
+
-- Apply to table
|
|
23
|
+
DROP TRIGGER IF EXISTS trigger_:table_name_updated_at ON :table_name;
|
|
24
|
+
CREATE TRIGGER trigger_:table_name_updated_at
|
|
25
|
+
BEFORE UPDATE ON :table_name
|
|
26
|
+
FOR EACH ROW
|
|
27
|
+
EXECUTE FUNCTION update_updated_at_column();
|
|
28
|
+
|
|
29
|
+
-- =============================================================================
|
|
30
|
+
-- AUDIT LOG TRIGGER
|
|
31
|
+
-- =============================================================================
|
|
32
|
+
|
|
33
|
+
-- Audit log table (if not exists)
|
|
34
|
+
CREATE TABLE IF NOT EXISTS audit_log (
|
|
35
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
36
|
+
table_name TEXT NOT NULL,
|
|
37
|
+
record_id UUID NOT NULL,
|
|
38
|
+
action TEXT NOT NULL CHECK (action IN ('INSERT', 'UPDATE', 'DELETE')),
|
|
39
|
+
old_data JSONB,
|
|
40
|
+
new_data JSONB,
|
|
41
|
+
changed_by UUID REFERENCES auth.users(id),
|
|
42
|
+
changed_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
CREATE INDEX IF NOT EXISTS idx_audit_log_table_record ON audit_log(table_name, record_id);
|
|
46
|
+
CREATE INDEX IF NOT EXISTS idx_audit_log_changed_at ON audit_log(changed_at);
|
|
47
|
+
|
|
48
|
+
-- Audit trigger function
|
|
49
|
+
CREATE OR REPLACE FUNCTION audit_trigger_function()
|
|
50
|
+
RETURNS TRIGGER AS $$
|
|
51
|
+
BEGIN
|
|
52
|
+
IF TG_OP = 'INSERT' THEN
|
|
53
|
+
INSERT INTO audit_log (table_name, record_id, action, new_data, changed_by)
|
|
54
|
+
VALUES (TG_TABLE_NAME, NEW.id, 'INSERT', to_jsonb(NEW), auth.uid());
|
|
55
|
+
RETURN NEW;
|
|
56
|
+
ELSIF TG_OP = 'UPDATE' THEN
|
|
57
|
+
INSERT INTO audit_log (table_name, record_id, action, old_data, new_data, changed_by)
|
|
58
|
+
VALUES (TG_TABLE_NAME, NEW.id, 'UPDATE', to_jsonb(OLD), to_jsonb(NEW), auth.uid());
|
|
59
|
+
RETURN NEW;
|
|
60
|
+
ELSIF TG_OP = 'DELETE' THEN
|
|
61
|
+
INSERT INTO audit_log (table_name, record_id, action, old_data, changed_by)
|
|
62
|
+
VALUES (TG_TABLE_NAME, OLD.id, 'DELETE', to_jsonb(OLD), auth.uid());
|
|
63
|
+
RETURN OLD;
|
|
64
|
+
END IF;
|
|
65
|
+
RETURN NULL;
|
|
66
|
+
END;
|
|
67
|
+
$$ LANGUAGE plpgsql SECURITY DEFINER;
|
|
68
|
+
|
|
69
|
+
-- Apply audit trigger to table
|
|
70
|
+
DROP TRIGGER IF EXISTS trigger_:table_name_audit ON :table_name;
|
|
71
|
+
CREATE TRIGGER trigger_:table_name_audit
|
|
72
|
+
AFTER INSERT OR UPDATE OR DELETE ON :table_name
|
|
73
|
+
FOR EACH ROW
|
|
74
|
+
EXECUTE FUNCTION audit_trigger_function();
|
|
75
|
+
|
|
76
|
+
-- =============================================================================
|
|
77
|
+
-- VALIDATION TRIGGER
|
|
78
|
+
-- =============================================================================
|
|
79
|
+
|
|
80
|
+
-- Custom validation trigger function
|
|
81
|
+
CREATE OR REPLACE FUNCTION :table_name_validation_trigger()
|
|
82
|
+
RETURNS TRIGGER AS $$
|
|
83
|
+
BEGIN
|
|
84
|
+
-- Example: Validate email format
|
|
85
|
+
IF NEW.email IS NOT NULL AND NEW.email !~ '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$' THEN
|
|
86
|
+
RAISE EXCEPTION 'Invalid email format: %', NEW.email;
|
|
87
|
+
END IF;
|
|
88
|
+
|
|
89
|
+
-- Example: Validate status transitions
|
|
90
|
+
IF TG_OP = 'UPDATE' AND OLD.status = 'completed' AND NEW.status != 'completed' THEN
|
|
91
|
+
RAISE EXCEPTION 'Cannot change status from completed';
|
|
92
|
+
END IF;
|
|
93
|
+
|
|
94
|
+
-- Example: Auto-set values
|
|
95
|
+
IF TG_OP = 'INSERT' THEN
|
|
96
|
+
NEW.created_by = COALESCE(NEW.created_by, auth.uid());
|
|
97
|
+
END IF;
|
|
98
|
+
|
|
99
|
+
RETURN NEW;
|
|
100
|
+
END;
|
|
101
|
+
$$ LANGUAGE plpgsql;
|
|
102
|
+
|
|
103
|
+
DROP TRIGGER IF EXISTS trigger_:table_name_validation ON :table_name;
|
|
104
|
+
CREATE TRIGGER trigger_:table_name_validation
|
|
105
|
+
BEFORE INSERT OR UPDATE ON :table_name
|
|
106
|
+
FOR EACH ROW
|
|
107
|
+
EXECUTE FUNCTION :table_name_validation_trigger();
|
|
108
|
+
|
|
109
|
+
-- =============================================================================
|
|
110
|
+
-- SOFT DELETE TRIGGER
|
|
111
|
+
-- =============================================================================
|
|
112
|
+
|
|
113
|
+
-- Instead of deleting, set deleted_at timestamp
|
|
114
|
+
CREATE OR REPLACE FUNCTION soft_delete_trigger()
|
|
115
|
+
RETURNS TRIGGER AS $$
|
|
116
|
+
BEGIN
|
|
117
|
+
-- Instead of DELETE, UPDATE with deleted_at
|
|
118
|
+
UPDATE :table_name
|
|
119
|
+
SET deleted_at = NOW()
|
|
120
|
+
WHERE id = OLD.id;
|
|
121
|
+
|
|
122
|
+
-- Return NULL to cancel the actual DELETE
|
|
123
|
+
RETURN NULL;
|
|
124
|
+
END;
|
|
125
|
+
$$ LANGUAGE plpgsql;
|
|
126
|
+
|
|
127
|
+
DROP TRIGGER IF EXISTS trigger_:table_name_soft_delete ON :table_name;
|
|
128
|
+
CREATE TRIGGER trigger_:table_name_soft_delete
|
|
129
|
+
BEFORE DELETE ON :table_name
|
|
130
|
+
FOR EACH ROW
|
|
131
|
+
EXECUTE FUNCTION soft_delete_trigger();
|
|
132
|
+
|
|
133
|
+
-- =============================================================================
|
|
134
|
+
-- NOTIFICATION TRIGGER (for Supabase Realtime)
|
|
135
|
+
-- =============================================================================
|
|
136
|
+
|
|
137
|
+
-- Notify on changes (Supabase handles this automatically for enabled tables)
|
|
138
|
+
-- Manual implementation if needed:
|
|
139
|
+
|
|
140
|
+
CREATE OR REPLACE FUNCTION notify_changes()
|
|
141
|
+
RETURNS TRIGGER AS $$
|
|
142
|
+
BEGIN
|
|
143
|
+
PERFORM pg_notify(
|
|
144
|
+
TG_TABLE_NAME || '_changes',
|
|
145
|
+
json_build_object(
|
|
146
|
+
'operation', TG_OP,
|
|
147
|
+
'record', CASE WHEN TG_OP = 'DELETE' THEN to_jsonb(OLD) ELSE to_jsonb(NEW) END
|
|
148
|
+
)::TEXT
|
|
149
|
+
);
|
|
150
|
+
RETURN COALESCE(NEW, OLD);
|
|
151
|
+
END;
|
|
152
|
+
$$ LANGUAGE plpgsql;
|