spine-framework 0.3.132 → 0.3.134
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/.framework/cli/commands/migrate.ts +3 -0
- package/.framework/functions/invites.ts +3 -2
- package/.framework/migrations/001_core_schema.sql +1 -1
- package/.framework/migrations/002_core_rls_and_functions.sql +0 -8
- package/.framework/migrations/004_core_rpc_and_broker.sql +2 -0
- package/.framework/migrations/010_auth_user_trigger.sql +82 -0
- package/.framework/src/pages/auth/RegisterPage.tsx +13 -14
- package/package.json +2 -1
|
@@ -103,14 +103,17 @@ export async function runMigrations(opts: { frameworkOnly?: boolean } = {}): Pro
|
|
|
103
103
|
const statements = splitSqlStatements(sql)
|
|
104
104
|
|
|
105
105
|
try {
|
|
106
|
+
await client.query('BEGIN')
|
|
106
107
|
for (const stmt of statements) await client.query(stmt)
|
|
107
108
|
await client.query(
|
|
108
109
|
`INSERT INTO _spine_migrations (filename) VALUES ($1) ON CONFLICT (filename) DO NOTHING`,
|
|
109
110
|
[name]
|
|
110
111
|
)
|
|
112
|
+
await client.query('COMMIT')
|
|
111
113
|
console.log('✓')
|
|
112
114
|
applied++
|
|
113
115
|
} catch (err: any) {
|
|
116
|
+
await client.query('ROLLBACK').catch(() => {})
|
|
114
117
|
console.log('✗')
|
|
115
118
|
console.error(` ${err.message}`)
|
|
116
119
|
failed++
|
|
@@ -175,12 +175,13 @@ export const checkDomain = createHandler(async (_ctx, _body) => {
|
|
|
175
175
|
|
|
176
176
|
const { data } = await adminDb
|
|
177
177
|
.from('people')
|
|
178
|
-
.select('
|
|
178
|
+
.select('account_id')
|
|
179
179
|
.ilike('email', `%@${domain}`)
|
|
180
180
|
.eq('status', 'active')
|
|
181
181
|
.limit(1)
|
|
182
182
|
|
|
183
|
-
|
|
183
|
+
const first = data?.[0]
|
|
184
|
+
return { match: !!first, account_id: first?.account_id ?? null }
|
|
184
185
|
})
|
|
185
186
|
// CIL[END chunk_id="invites_checkdomain_1783968140"]
|
|
186
187
|
// CIL[BEGIN chunk_id="invites_validatetoken_1783968140" type="function" identity="validateToken" parent="none"]
|
|
@@ -319,7 +319,7 @@ CREATE TABLE IF NOT EXISTS public.account_paths (
|
|
|
319
319
|
ancestor_id uuid NOT NULL REFERENCES public.accounts(id) ON DELETE CASCADE,
|
|
320
320
|
descendant_id uuid NOT NULL REFERENCES public.accounts(id) ON DELETE CASCADE,
|
|
321
321
|
depth integer NOT NULL,
|
|
322
|
-
PRIMARY KEY (ancestor_id, descendant_id)
|
|
322
|
+
PRIMARY KEY (ancestor_id, descendant_id),
|
|
323
323
|
CONSTRAINT chk_account_paths_no_self CHECK (ancestor_id != descendant_id)
|
|
324
324
|
);
|
|
325
325
|
CREATE INDEX IF NOT EXISTS idx_accounts_parent_id ON public.accounts(parent_id);
|
|
@@ -530,10 +530,6 @@ CREATE TRIGGER set_updated_at_account_paths
|
|
|
530
530
|
BEFORE UPDATE ON public.account_paths
|
|
531
531
|
FOR EACH ROW EXECUTE FUNCTION public.set_updated_at();
|
|
532
532
|
|
|
533
|
-
DROP TRIGGER IF EXISTS set_updated_at_people_roles ON public.people_roles;
|
|
534
|
-
CREATE TRIGGER set_updated_at_people_roles
|
|
535
|
-
BEFORE UPDATE ON public.people_roles
|
|
536
|
-
FOR EACH ROW EXECUTE FUNCTION public.set_updated_at();
|
|
537
533
|
|
|
538
534
|
-- RLS policies
|
|
539
535
|
|
|
@@ -634,10 +630,6 @@ CREATE POLICY api_keys_access ON public.api_keys FOR ALL
|
|
|
634
630
|
USING (account_id IN (SELECT public.get_accessible_accounts(auth.uid()))
|
|
635
631
|
OR id = auth.uid() );
|
|
636
632
|
|
|
637
|
-
CREATE POLICY akul_access ON public.api_key_usage_logs FOR ALL
|
|
638
|
-
USING (account_id IN (SELECT public.get_accessible_accounts(auth.uid()))
|
|
639
|
-
);
|
|
640
|
-
|
|
641
633
|
CREATE POLICY logs_access ON public.logs FOR ALL
|
|
642
634
|
USING (account_id IN (SELECT public.get_accessible_accounts(auth.uid()))
|
|
643
635
|
);
|
|
@@ -122,6 +122,7 @@ COMMENT ON COLUMN public.integrations.is_configured IS 'True when required crede
|
|
|
122
122
|
ALTER TABLE public.integrations DROP CONSTRAINT IF EXISTS integrations_app_id_slug_key;
|
|
123
123
|
ALTER TABLE public.integrations ADD CONSTRAINT integrations_app_account_slug_key UNIQUE (app_id, account_id, slug);
|
|
124
124
|
|
|
125
|
+
ALTER TABLE public.integrations DROP CONSTRAINT IF EXISTS fk_integrations_created_by;
|
|
125
126
|
ALTER TABLE public.integrations ADD CONSTRAINT fk_integrations_created_by
|
|
126
127
|
FOREIGN KEY (created_by) REFERENCES public.people(id) ON DELETE SET NULL;
|
|
127
128
|
|
|
@@ -153,6 +154,7 @@ COMMENT ON COLUMN public.api_keys.rate_limit IS 'Requests per rate_limit_window_
|
|
|
153
154
|
|
|
154
155
|
ALTER TABLE public.api_keys ADD CONSTRAINT fk_api_keys_integration
|
|
155
156
|
FOREIGN KEY (integration_id) REFERENCES public.integrations(id) ON DELETE CASCADE;
|
|
157
|
+
ALTER TABLE public.api_keys DROP CONSTRAINT IF EXISTS fk_api_keys_created_by;
|
|
156
158
|
ALTER TABLE public.api_keys ADD CONSTRAINT fk_api_keys_created_by
|
|
157
159
|
FOREIGN KEY (created_by) REFERENCES public.people(id) ON DELETE SET NULL;
|
|
158
160
|
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
-- Create trigger function to sync new auth users to people records
|
|
2
|
+
CREATE OR REPLACE FUNCTION public.handle_new_auth_user()
|
|
3
|
+
RETURNS TRIGGER LANGUAGE plpgsql SECURITY DEFINER SET search_path = public AS $$
|
|
4
|
+
DECLARE
|
|
5
|
+
v_account_id uuid;
|
|
6
|
+
v_type_id uuid;
|
|
7
|
+
v_role_id uuid;
|
|
8
|
+
v_is_first boolean;
|
|
9
|
+
v_design_schema jsonb;
|
|
10
|
+
v_validation_schema jsonb;
|
|
11
|
+
v_stub_id uuid;
|
|
12
|
+
v_stub_status text;
|
|
13
|
+
BEGIN
|
|
14
|
+
SELECT id INTO v_account_id FROM public.accounts WHERE slug = 'spine-system' LIMIT 1;
|
|
15
|
+
SELECT id, design_schema, validation_schema
|
|
16
|
+
INTO v_type_id, v_design_schema, v_validation_schema
|
|
17
|
+
FROM public.types WHERE kind = 'person' AND slug = 'person' LIMIT 1;
|
|
18
|
+
|
|
19
|
+
IF v_account_id IS NULL OR v_type_id IS NULL THEN
|
|
20
|
+
RAISE WARNING 'handle_new_auth_user: spine-system account or person type not found — skipping';
|
|
21
|
+
RETURN NEW;
|
|
22
|
+
END IF;
|
|
23
|
+
|
|
24
|
+
SELECT id, status
|
|
25
|
+
INTO v_stub_id, v_stub_status
|
|
26
|
+
FROM public.people
|
|
27
|
+
WHERE email = NEW.email
|
|
28
|
+
AND status IN ('invited', 'pending')
|
|
29
|
+
AND auth_uid IS NULL
|
|
30
|
+
LIMIT 1;
|
|
31
|
+
|
|
32
|
+
IF v_stub_id IS NOT NULL THEN
|
|
33
|
+
UPDATE public.people SET
|
|
34
|
+
auth_uid = NEW.id,
|
|
35
|
+
full_name = COALESCE(NEW.raw_user_meta_data->>'full_name', full_name, split_part(NEW.email, '@', 1)),
|
|
36
|
+
status = CASE WHEN v_stub_status = 'invited' THEN 'active' ELSE 'pending' END,
|
|
37
|
+
updated_at = now()
|
|
38
|
+
WHERE id = v_stub_id;
|
|
39
|
+
|
|
40
|
+
RETURN NEW;
|
|
41
|
+
END IF;
|
|
42
|
+
|
|
43
|
+
SELECT NOT EXISTS (SELECT 1 FROM public.people LIMIT 1) INTO v_is_first;
|
|
44
|
+
IF v_is_first THEN
|
|
45
|
+
SELECT id INTO v_role_id FROM public.roles WHERE slug = 'system_admin' LIMIT 1;
|
|
46
|
+
END IF;
|
|
47
|
+
|
|
48
|
+
INSERT INTO public.people (
|
|
49
|
+
id,
|
|
50
|
+
auth_uid,
|
|
51
|
+
email,
|
|
52
|
+
full_name,
|
|
53
|
+
account_id,
|
|
54
|
+
type_id,
|
|
55
|
+
role_id,
|
|
56
|
+
status,
|
|
57
|
+
is_active,
|
|
58
|
+
design_schema,
|
|
59
|
+
validation_schema
|
|
60
|
+
) VALUES (
|
|
61
|
+
NEW.id,
|
|
62
|
+
NEW.id,
|
|
63
|
+
NEW.email,
|
|
64
|
+
COALESCE(NEW.raw_user_meta_data->>'full_name', split_part(NEW.email, '@', 1)),
|
|
65
|
+
v_account_id,
|
|
66
|
+
v_type_id,
|
|
67
|
+
v_role_id,
|
|
68
|
+
'active',
|
|
69
|
+
true,
|
|
70
|
+
v_design_schema,
|
|
71
|
+
COALESCE(v_validation_schema, '{}'::jsonb)
|
|
72
|
+
)
|
|
73
|
+
ON CONFLICT (id) DO NOTHING;
|
|
74
|
+
|
|
75
|
+
RETURN NEW;
|
|
76
|
+
END;
|
|
77
|
+
$$;
|
|
78
|
+
|
|
79
|
+
DROP TRIGGER IF EXISTS on_auth_user_created ON auth.users;
|
|
80
|
+
CREATE TRIGGER on_auth_user_created
|
|
81
|
+
AFTER INSERT ON auth.users
|
|
82
|
+
FOR EACH ROW EXECUTE FUNCTION public.handle_new_auth_user();
|
|
@@ -262,8 +262,8 @@ export function RegisterPage() {
|
|
|
262
262
|
const result = await callInvitesApi('check-domain', { email })
|
|
263
263
|
setDomainCheckLoading(false)
|
|
264
264
|
|
|
265
|
-
if (result.data?.match) {
|
|
266
|
-
setDomainMatchAccountId(
|
|
265
|
+
if (result.data?.match && result.data?.account_id) {
|
|
266
|
+
setDomainMatchAccountId(result.data.account_id)
|
|
267
267
|
}
|
|
268
268
|
}, [email, inviteToken, useType, isFirstUser])
|
|
269
269
|
|
|
@@ -323,23 +323,22 @@ export function RegisterPage() {
|
|
|
323
323
|
|
|
324
324
|
const completeRegistrationParams: any = {
|
|
325
325
|
email: resolvedEmail,
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
role: effectiveRole,
|
|
329
|
-
redirect: getPostRegistrationRedirect(regConfig, searchParams, false, effectiveRole),
|
|
326
|
+
path: 'new_account',
|
|
327
|
+
role_slug: effectiveRole,
|
|
330
328
|
}
|
|
331
329
|
|
|
332
330
|
if (useType === 'company') {
|
|
333
|
-
completeRegistrationParams.account_name = companyName
|
|
331
|
+
completeRegistrationParams.account_name = companyName || fullName || email.split('@')[0]
|
|
332
|
+
} else {
|
|
333
|
+
completeRegistrationParams.account_name = fullName || email.split('@')[0]
|
|
334
334
|
}
|
|
335
335
|
|
|
336
336
|
if (effectiveAccountStrategy === 'existing' && targetAccount) {
|
|
337
|
-
completeRegistrationParams.
|
|
338
|
-
completeRegistrationParams.
|
|
339
|
-
} else if (domainMatchAccountId
|
|
340
|
-
completeRegistrationParams.
|
|
341
|
-
|
|
342
|
-
completeRegistrationParams.strategy = 'new_account'
|
|
337
|
+
completeRegistrationParams.path = 'existing_account'
|
|
338
|
+
completeRegistrationParams.target_account = targetAccount
|
|
339
|
+
} else if (domainMatchAccountId && domainMatchChoice === 'join') {
|
|
340
|
+
completeRegistrationParams.path = 'join_request'
|
|
341
|
+
completeRegistrationParams.target_account_id = domainMatchAccountId
|
|
343
342
|
}
|
|
344
343
|
|
|
345
344
|
const result = await callInvitesApi('complete-registration', completeRegistrationParams, 'POST')
|
|
@@ -367,7 +366,7 @@ export function RegisterPage() {
|
|
|
367
366
|
const isInvite = !!inviteToken && !tokenError
|
|
368
367
|
const showPersonalCompanyToggle = !isInvite && !isFirstUser
|
|
369
368
|
const showCompanyName = showPersonalCompanyToggle && useType === 'company'
|
|
370
|
-
const showDomainMatchChoice = domainMatchAccountId
|
|
369
|
+
const showDomainMatchChoice = !!domainMatchAccountId && !isInvite && !isFirstUser
|
|
371
370
|
|
|
372
371
|
if (tokenLoading || isFirstUser === null || (!isFirstUser && !isInvite && !regConfig)) {
|
|
373
372
|
return (
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "spine-framework",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.134",
|
|
4
4
|
"description": "Multi-tenant, modular application platform for modern SaaS systems",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
".framework/migrations/007_kb_article_list.sql",
|
|
26
26
|
".framework/migrations/008_items_target.sql",
|
|
27
27
|
".framework/migrations/009_roles_slug_unique.sql",
|
|
28
|
+
".framework/migrations/010_auth_user_trigger.sql",
|
|
28
29
|
".framework/public/",
|
|
29
30
|
".framework/scripts/",
|
|
30
31
|
".framework/seeds/",
|