create-bw-app 0.24.8 → 0.25.0
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/README.md +3 -1
- package/package.json +1 -1
- package/src/bw.mjs +1 -1
- package/src/constants.mjs +14 -9
- package/src/generator.mjs +3 -6
- package/src/migrations.mjs +43 -5
- package/src/upgrade.mjs +54 -2
- package/template/base/app/(shell)/dashboard/dashboard-page-mount.tsx +7 -0
- package/template/base/app/(shell)/dashboard/page.tsx +1 -1
- package/template/base/app/(shell)/shell-layout-client.tsx +28 -7
- package/template/modules/crm/app/(shell)/crm/organizations/[id]/page.tsx +1 -1
- package/template/modules/crm/app/(shell)/crm/organizations/page.tsx +1 -3
- package/template/modules/crm/app/(shell)/crm/page.tsx +1 -5
- package/template/modules/crm/app/(shell)/crm/report/page.tsx +1 -5
- package/template/modules/projects/app/(shell)/account/page.tsx +5 -1
- package/template/modules/projects/app/(shell)/account/perfil/page.tsx +1 -0
- package/template/modules/projects/app/(shell)/projetos/projetos-server-mounts.tsx +3 -2
- package/template/modules/projects/app/api/account/projects/[id]/route.ts +2 -0
- package/template/modules/projects/app/api/account/projects/route.ts +2 -0
- package/template/modules/projects/app/api/projects/[id]/client-access/route.ts +2 -0
- package/template/modules/projects/app/api/projects/[id]/organizations/route.ts +2 -0
- package/template/modules/projects/app/api/projects/_handlers.ts +24 -0
- package/template/modules/projects/app/api/projects/setup-options/route.ts +2 -0
- package/template/supabase/modules/orgs/migrations/20260812120000_staff_only_organization_access_management.sql +7 -0
- package/template/supabase/modules/projects/migrations/20260810120000_project_client_access.sql +328 -0
- package/template/supabase/modules/projects/migrations/20260811120000_project_client_access_expand.sql +1061 -0
- package/template/supabase/modules/projects/migrations/20260811120500_project_member_sync_hardening.sql +156 -0
- package/template/supabase/modules/projects/migrations/20260811121000_project_client_access_enforcement.sql +651 -0
- package/template/supabase/modules/projects/migrations/20260811121500_project_client_organization_memberships.sql +29 -0
- package/template/supabase/modules/projects/migrations/20260811121700_project_client_meta_preview.sql +193 -0
- package/template/supabase/modules/projects/migrations/20260811122000_project_client_access_identity_cleanup.sql +201 -0
- package/template/supabase/modules/projects/migrations/20260811122500_remove_project_client_next_steps.sql +681 -0
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
-- Make project team and owner changes a single explicitly authorized operation.
|
|
2
|
+
|
|
3
|
+
CREATE OR REPLACE FUNCTION public.sync_project_members_exact(
|
|
4
|
+
p_project_id uuid,
|
|
5
|
+
p_members jsonb,
|
|
6
|
+
p_owner_profile_id uuid DEFAULT NULL
|
|
7
|
+
)
|
|
8
|
+
RETURNS void
|
|
9
|
+
LANGUAGE plpgsql
|
|
10
|
+
SECURITY DEFINER
|
|
11
|
+
SET search_path = public
|
|
12
|
+
AS $$
|
|
13
|
+
DECLARE
|
|
14
|
+
v_actor_profile_id uuid := public.current_profile_id();
|
|
15
|
+
v_current_owner_profile_id uuid;
|
|
16
|
+
v_client_contact_profile_id uuid;
|
|
17
|
+
v_members jsonb := COALESCE(p_members, '[]'::jsonb);
|
|
18
|
+
BEGIN
|
|
19
|
+
IF jsonb_typeof(v_members) <> 'array' THEN
|
|
20
|
+
RAISE EXCEPTION 'Project members must be a JSON array.' USING ERRCODE = '22023';
|
|
21
|
+
END IF;
|
|
22
|
+
|
|
23
|
+
SELECT project.owner_profile_id, project.client_contact_profile_id
|
|
24
|
+
INTO v_current_owner_profile_id, v_client_contact_profile_id
|
|
25
|
+
FROM public.projects project
|
|
26
|
+
WHERE project.id = p_project_id
|
|
27
|
+
FOR UPDATE;
|
|
28
|
+
IF NOT FOUND THEN
|
|
29
|
+
RAISE EXCEPTION 'Projeto não encontrado.' USING ERRCODE = 'P0002';
|
|
30
|
+
END IF;
|
|
31
|
+
|
|
32
|
+
IF NOT (
|
|
33
|
+
public.is_admin()
|
|
34
|
+
OR (
|
|
35
|
+
public.is_staff()
|
|
36
|
+
AND (
|
|
37
|
+
v_current_owner_profile_id = v_actor_profile_id
|
|
38
|
+
OR EXISTS (
|
|
39
|
+
SELECT 1
|
|
40
|
+
FROM public.project_members member
|
|
41
|
+
WHERE member.project_id = p_project_id
|
|
42
|
+
AND member.profile_id = v_actor_profile_id
|
|
43
|
+
AND member.role = 'owner'
|
|
44
|
+
)
|
|
45
|
+
)
|
|
46
|
+
)
|
|
47
|
+
) THEN
|
|
48
|
+
RAISE EXCEPTION 'Only project owners or administrators can manage the internal project team.'
|
|
49
|
+
USING ERRCODE = '42501';
|
|
50
|
+
END IF;
|
|
51
|
+
|
|
52
|
+
IF EXISTS (
|
|
53
|
+
SELECT 1
|
|
54
|
+
FROM jsonb_to_recordset(v_members) AS member(profile_id uuid, role text)
|
|
55
|
+
WHERE member.profile_id IS NULL
|
|
56
|
+
OR member.role NOT IN ('owner', 'contributor', 'observer')
|
|
57
|
+
) THEN
|
|
58
|
+
RAISE EXCEPTION 'Every project member requires a valid profile and role.'
|
|
59
|
+
USING ERRCODE = '22023';
|
|
60
|
+
END IF;
|
|
61
|
+
|
|
62
|
+
IF (
|
|
63
|
+
SELECT count(*) <> count(DISTINCT member.profile_id)
|
|
64
|
+
FROM jsonb_to_recordset(v_members) AS member(profile_id uuid, role text)
|
|
65
|
+
) THEN
|
|
66
|
+
RAISE EXCEPTION 'Each internal profile can appear only once in the project team.'
|
|
67
|
+
USING ERRCODE = '22023';
|
|
68
|
+
END IF;
|
|
69
|
+
|
|
70
|
+
IF (SELECT count(*) FROM jsonb_to_recordset(v_members) AS member(profile_id uuid, role text) WHERE member.role = 'owner') <> 1
|
|
71
|
+
OR NOT EXISTS (
|
|
72
|
+
SELECT 1
|
|
73
|
+
FROM jsonb_to_recordset(v_members) AS member(profile_id uuid, role text)
|
|
74
|
+
WHERE member.profile_id = p_owner_profile_id
|
|
75
|
+
AND member.role = 'owner'
|
|
76
|
+
) THEN
|
|
77
|
+
RAISE EXCEPTION 'Exactly one internal project owner is required and must match the project owner.'
|
|
78
|
+
USING ERRCODE = '22023';
|
|
79
|
+
END IF;
|
|
80
|
+
|
|
81
|
+
IF EXISTS (
|
|
82
|
+
SELECT 1
|
|
83
|
+
FROM jsonb_to_recordset(v_members) AS member(profile_id uuid, role text)
|
|
84
|
+
LEFT JOIN public.user_role_assignments role_assignment
|
|
85
|
+
ON role_assignment.profile_id = member.profile_id
|
|
86
|
+
AND role_assignment.role_code IN ('staff', 'admin')
|
|
87
|
+
WHERE role_assignment.id IS NULL
|
|
88
|
+
) THEN
|
|
89
|
+
RAISE EXCEPTION 'Project members must be internal staff.' USING ERRCODE = '22023';
|
|
90
|
+
END IF;
|
|
91
|
+
|
|
92
|
+
IF v_client_contact_profile_id IS NOT NULL AND NOT EXISTS (
|
|
93
|
+
SELECT 1
|
|
94
|
+
FROM jsonb_to_recordset(v_members) AS member(profile_id uuid, role text)
|
|
95
|
+
WHERE member.profile_id = v_client_contact_profile_id
|
|
96
|
+
) THEN
|
|
97
|
+
RAISE EXCEPTION 'The responsible client contact must remain in the internal project team.'
|
|
98
|
+
USING ERRCODE = '22023';
|
|
99
|
+
END IF;
|
|
100
|
+
|
|
101
|
+
INSERT INTO public.project_members(project_id, profile_id, role)
|
|
102
|
+
SELECT p_project_id, member.profile_id, member.role
|
|
103
|
+
FROM jsonb_to_recordset(v_members) AS member(profile_id uuid, role text)
|
|
104
|
+
ON CONFLICT (project_id, profile_id) DO UPDATE SET role = EXCLUDED.role;
|
|
105
|
+
|
|
106
|
+
DELETE FROM public.project_members existing
|
|
107
|
+
WHERE existing.project_id = p_project_id
|
|
108
|
+
AND NOT EXISTS (
|
|
109
|
+
SELECT 1
|
|
110
|
+
FROM jsonb_to_recordset(v_members) AS member(profile_id uuid, role text)
|
|
111
|
+
WHERE member.profile_id = existing.profile_id
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
UPDATE public.projects
|
|
115
|
+
SET owner_profile_id = p_owner_profile_id
|
|
116
|
+
WHERE id = p_project_id;
|
|
117
|
+
END;
|
|
118
|
+
$$;
|
|
119
|
+
|
|
120
|
+
REVOKE ALL ON FUNCTION public.sync_project_members_exact(uuid, jsonb, uuid)
|
|
121
|
+
FROM PUBLIC, anon, service_role;
|
|
122
|
+
GRANT EXECUTE ON FUNCTION public.sync_project_members_exact(uuid, jsonb, uuid)
|
|
123
|
+
TO authenticated;
|
|
124
|
+
|
|
125
|
+
CREATE OR REPLACE FUNCTION public.prevent_internal_project_role_downgrade()
|
|
126
|
+
RETURNS trigger
|
|
127
|
+
LANGUAGE plpgsql
|
|
128
|
+
SECURITY DEFINER
|
|
129
|
+
SET search_path = public
|
|
130
|
+
AS $$
|
|
131
|
+
BEGIN
|
|
132
|
+
IF OLD.role_code IN ('staff', 'admin')
|
|
133
|
+
AND (TG_OP = 'DELETE' OR NEW.role_code NOT IN ('staff', 'admin'))
|
|
134
|
+
AND (
|
|
135
|
+
EXISTS (
|
|
136
|
+
SELECT 1 FROM public.projects project
|
|
137
|
+
WHERE project.owner_profile_id = OLD.profile_id
|
|
138
|
+
)
|
|
139
|
+
OR EXISTS (
|
|
140
|
+
SELECT 1 FROM public.project_members member
|
|
141
|
+
WHERE member.profile_id = OLD.profile_id
|
|
142
|
+
)
|
|
143
|
+
) THEN
|
|
144
|
+
RAISE EXCEPTION 'Reassign or remove this profile from every project before changing its role to client.'
|
|
145
|
+
USING ERRCODE = '23514';
|
|
146
|
+
END IF;
|
|
147
|
+
RETURN CASE WHEN TG_OP = 'DELETE' THEN OLD ELSE NEW END;
|
|
148
|
+
END;
|
|
149
|
+
$$;
|
|
150
|
+
|
|
151
|
+
DROP TRIGGER IF EXISTS trg_prevent_internal_project_role_downgrade
|
|
152
|
+
ON public.user_role_assignments;
|
|
153
|
+
CREATE TRIGGER trg_prevent_internal_project_role_downgrade
|
|
154
|
+
BEFORE UPDATE OF role_code OR DELETE ON public.user_role_assignments
|
|
155
|
+
FOR EACH ROW
|
|
156
|
+
EXECUTE FUNCTION public.prevent_internal_project_role_downgrade();
|