spine-framework 0.3.92 → 0.3.93
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.
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
-- Migration 019: Add unique constraint on triggers(app_id, name)
|
|
2
|
+
--
|
|
3
|
+
-- The triggers table had no unique constraint, making idempotent upserts
|
|
4
|
+
-- impossible (re-runs would create duplicate rows). This adds a partial unique
|
|
5
|
+
-- index scoped to (app_id, name) so install-app can safely upsert seed triggers
|
|
6
|
+
-- using onConflict: 'app_id,name'.
|
|
7
|
+
--
|
|
8
|
+
-- NULL app_id rows (global triggers) are excluded from the uniqueness check
|
|
9
|
+
-- via the WHERE clause to avoid false conflicts between global triggers.
|
|
10
|
+
|
|
11
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_triggers_app_id_name
|
|
12
|
+
ON public.triggers(app_id, name)
|
|
13
|
+
WHERE app_id IS NOT NULL;
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
-- Migration 020: Add role-based filtering to get_account_apps()
|
|
2
|
+
--
|
|
3
|
+
-- Previously the RPC returned all apps for an account and the frontend
|
|
4
|
+
-- filtered by role client-side. This was fragile (race conditions around
|
|
5
|
+
-- stale roles) and leaked app metadata to users who couldn't access them.
|
|
6
|
+
--
|
|
7
|
+
-- New signature adds two optional params:
|
|
8
|
+
-- user_role_slugs text[] — the calling user's role slugs (e.g. ARRAY['support'])
|
|
9
|
+
-- is_system_admin boolean — true bypasses all role checks
|
|
10
|
+
--
|
|
11
|
+
-- Filtering logic:
|
|
12
|
+
-- 1. system_admin always sees everything
|
|
13
|
+
-- 2. Apps with no required_roles AND no min_role are visible to everyone
|
|
14
|
+
-- 3. Apps with required_roles: user must have at least one matching role
|
|
15
|
+
-- 4. Apps with only min_role (legacy): treated as required_roles = ARRAY[min_role]
|
|
16
|
+
--
|
|
17
|
+
-- Existing callers that omit the new params get the old behaviour (no filtering).
|
|
18
|
+
|
|
19
|
+
DROP FUNCTION IF EXISTS public.get_account_apps(uuid, boolean, boolean);
|
|
20
|
+
DROP FUNCTION IF EXISTS public.get_account_apps(uuid, boolean, boolean, text[], boolean);
|
|
21
|
+
|
|
22
|
+
CREATE OR REPLACE FUNCTION public.get_account_apps(
|
|
23
|
+
account_id uuid,
|
|
24
|
+
include_system boolean DEFAULT true,
|
|
25
|
+
include_inactive boolean DEFAULT false,
|
|
26
|
+
user_role_slugs text[] DEFAULT NULL,
|
|
27
|
+
is_system_admin boolean DEFAULT false
|
|
28
|
+
)
|
|
29
|
+
RETURNS TABLE(
|
|
30
|
+
id uuid,
|
|
31
|
+
slug text,
|
|
32
|
+
name text,
|
|
33
|
+
description text,
|
|
34
|
+
icon text,
|
|
35
|
+
color text,
|
|
36
|
+
version text,
|
|
37
|
+
app_type text,
|
|
38
|
+
source text,
|
|
39
|
+
owner_account_id uuid,
|
|
40
|
+
is_active boolean,
|
|
41
|
+
is_system boolean,
|
|
42
|
+
min_role text,
|
|
43
|
+
required_roles jsonb,
|
|
44
|
+
config jsonb,
|
|
45
|
+
nav_items jsonb,
|
|
46
|
+
route_prefix text,
|
|
47
|
+
renderer text,
|
|
48
|
+
metadata jsonb,
|
|
49
|
+
integration_deps jsonb,
|
|
50
|
+
created_at timestamptz
|
|
51
|
+
)
|
|
52
|
+
LANGUAGE plpgsql
|
|
53
|
+
SECURITY DEFINER
|
|
54
|
+
AS $$
|
|
55
|
+
BEGIN
|
|
56
|
+
RETURN QUERY
|
|
57
|
+
SELECT
|
|
58
|
+
a.id,
|
|
59
|
+
a.slug,
|
|
60
|
+
a.name,
|
|
61
|
+
a.description,
|
|
62
|
+
a.icon,
|
|
63
|
+
a.color,
|
|
64
|
+
a.version,
|
|
65
|
+
a.app_type,
|
|
66
|
+
a.source,
|
|
67
|
+
a.owner_account_id,
|
|
68
|
+
a.is_active,
|
|
69
|
+
a.is_system,
|
|
70
|
+
a.min_role,
|
|
71
|
+
a.required_roles,
|
|
72
|
+
a.config,
|
|
73
|
+
a.nav_items,
|
|
74
|
+
a.route_prefix,
|
|
75
|
+
a.renderer,
|
|
76
|
+
a.metadata,
|
|
77
|
+
a.integration_deps,
|
|
78
|
+
a.created_at
|
|
79
|
+
FROM public.apps a
|
|
80
|
+
WHERE
|
|
81
|
+
-- Active filter
|
|
82
|
+
(include_inactive OR a.is_active = true)
|
|
83
|
+
-- Ownership / visibility filter
|
|
84
|
+
AND (
|
|
85
|
+
a.is_system = true
|
|
86
|
+
OR a.owner_account_id = get_account_apps.account_id
|
|
87
|
+
OR a.owner_account_id IS NULL
|
|
88
|
+
)
|
|
89
|
+
-- Role filter (only applied when user_role_slugs is provided)
|
|
90
|
+
AND (
|
|
91
|
+
user_role_slugs IS NULL -- no filter requested (backward compat)
|
|
92
|
+
OR is_system_admin -- system_admin sees everything
|
|
93
|
+
OR ( -- no role requirement on this app
|
|
94
|
+
(a.required_roles IS NULL OR a.required_roles = '[]'::jsonb)
|
|
95
|
+
AND a.min_role IS NULL
|
|
96
|
+
)
|
|
97
|
+
OR ( -- user has a matching required_role
|
|
98
|
+
a.required_roles IS NOT NULL
|
|
99
|
+
AND a.required_roles != '[]'::jsonb
|
|
100
|
+
AND EXISTS (
|
|
101
|
+
SELECT 1
|
|
102
|
+
FROM jsonb_array_elements_text(a.required_roles) AS rr(role_slug)
|
|
103
|
+
WHERE rr.role_slug = ANY(user_role_slugs)
|
|
104
|
+
)
|
|
105
|
+
)
|
|
106
|
+
OR ( -- legacy min_role fallback
|
|
107
|
+
(a.required_roles IS NULL OR a.required_roles = '[]'::jsonb)
|
|
108
|
+
AND a.min_role IS NOT NULL
|
|
109
|
+
AND a.min_role = ANY(user_role_slugs)
|
|
110
|
+
)
|
|
111
|
+
)
|
|
112
|
+
ORDER BY
|
|
113
|
+
a.is_system DESC,
|
|
114
|
+
a.app_type,
|
|
115
|
+
a.name;
|
|
116
|
+
END;
|
|
117
|
+
$$;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "spine-framework",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.93",
|
|
4
4
|
"description": "Multi-tenant, modular application platform for modern SaaS systems",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -25,6 +25,8 @@
|
|
|
25
25
|
".framework/migrations/016_invites.sql",
|
|
26
26
|
".framework/migrations/017_fix_get_account_apps.sql",
|
|
27
27
|
".framework/migrations/018_apps_required_roles.sql",
|
|
28
|
+
".framework/migrations/019_triggers_unique_constraint.sql",
|
|
29
|
+
".framework/migrations/020_get_account_apps_role_filter.sql",
|
|
28
30
|
".framework/public/",
|
|
29
31
|
".framework/scripts/",
|
|
30
32
|
".framework/seeds/",
|