@plutocms/supabase 0.0.1-alpha.8 → 0.0.1-alpha.9
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/app/middleware/auth.ts +5 -1
- package/app/pages/admin/setup.vue +163 -23
- package/app/pages/admin.vue +1 -0
- package/package.json +1 -1
- package/public/schema.sql +22 -5
- package/server/api/setup/create.post.ts +0 -7
- package/server/api/signup.post.ts +35 -8
package/app/middleware/auth.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
|
+
import type { RouteLocationNormalizedGeneric } from 'vue-router'
|
|
2
|
+
|
|
1
3
|
export default defineNuxtRouteMiddleware(async (to, from) => {
|
|
2
|
-
const { isLoggedIn, logout } = await useAuth({
|
|
4
|
+
const { isLoggedIn, logout } = await useAuth({
|
|
5
|
+
route: to as RouteLocationNormalizedGeneric,
|
|
6
|
+
})
|
|
3
7
|
|
|
4
8
|
if (
|
|
5
9
|
isLoggedIn.value &&
|
|
@@ -23,10 +23,10 @@ const items = ref<StepperItem[]>([
|
|
|
23
23
|
icon: 'i-lucide-database',
|
|
24
24
|
},
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
{
|
|
27
27
|
title: 'Admin Account',
|
|
28
28
|
icon: 'i-lucide-user',
|
|
29
|
-
},
|
|
29
|
+
},
|
|
30
30
|
|
|
31
31
|
{
|
|
32
32
|
title: 'Complete Setup',
|
|
@@ -39,17 +39,29 @@ const stepper = useTemplateRef('stepper')
|
|
|
39
39
|
const currentStep = ref<number>(0)
|
|
40
40
|
|
|
41
41
|
const isSettingUpDatabase = ref(false)
|
|
42
|
+
const isCreatingAdmin = ref(false)
|
|
42
43
|
|
|
43
44
|
const databaseForm = ref({
|
|
44
45
|
connectionString: '',
|
|
45
46
|
password: '',
|
|
46
47
|
})
|
|
47
48
|
|
|
49
|
+
const adminForm = ref({
|
|
50
|
+
username: '',
|
|
51
|
+
display_name: '',
|
|
52
|
+
email: '',
|
|
53
|
+
password: '',
|
|
54
|
+
})
|
|
55
|
+
|
|
48
56
|
const currentLoading = computed(() => {
|
|
49
57
|
if (currentStep.value === 0) {
|
|
50
58
|
return isSettingUpDatabase.value
|
|
51
59
|
}
|
|
52
60
|
|
|
61
|
+
if (currentStep.value === 1) {
|
|
62
|
+
return isCreatingAdmin.value
|
|
63
|
+
}
|
|
64
|
+
|
|
53
65
|
return false
|
|
54
66
|
})
|
|
55
67
|
|
|
@@ -58,10 +70,26 @@ const currentStepId = computed(() => {
|
|
|
58
70
|
return 'database-setup-form'
|
|
59
71
|
}
|
|
60
72
|
|
|
73
|
+
if (currentStep.value === 1) {
|
|
74
|
+
return 'admin-setup-form'
|
|
75
|
+
}
|
|
76
|
+
|
|
61
77
|
return undefined
|
|
62
78
|
})
|
|
63
79
|
|
|
64
|
-
|
|
80
|
+
const currentStepSubmitLabel = computed(() => {
|
|
81
|
+
if (currentStep.value === 0) {
|
|
82
|
+
return 'Set up database'
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (currentStep.value === 1) {
|
|
86
|
+
return 'Create admin account'
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return 'Submit'
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
async function completeDatabaseSetup() {
|
|
65
93
|
try {
|
|
66
94
|
isSettingUpDatabase.value = true
|
|
67
95
|
|
|
@@ -73,6 +101,7 @@ async function completeSetup() {
|
|
|
73
101
|
'[YOUR-PASSWORD]',
|
|
74
102
|
encodeURIComponent(databaseForm.value.password)
|
|
75
103
|
),
|
|
104
|
+
adminForm: adminForm.value,
|
|
76
105
|
},
|
|
77
106
|
})
|
|
78
107
|
|
|
@@ -111,9 +140,63 @@ async function completeSetup() {
|
|
|
111
140
|
}
|
|
112
141
|
}
|
|
113
142
|
|
|
143
|
+
async function createAdminAccount() {
|
|
144
|
+
try {
|
|
145
|
+
isCreatingAdmin.value = true
|
|
146
|
+
|
|
147
|
+
const data = await $fetch('/api/signup', {
|
|
148
|
+
method: 'POST',
|
|
149
|
+
body: {
|
|
150
|
+
username: adminForm.value.username,
|
|
151
|
+
display_name: adminForm.value.display_name,
|
|
152
|
+
email: adminForm.value.email,
|
|
153
|
+
password: adminForm.value.password,
|
|
154
|
+
is_admin: true,
|
|
155
|
+
},
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
if (data.success === false) {
|
|
159
|
+
toast.add({
|
|
160
|
+
title: 'Admin account creation failed',
|
|
161
|
+
description: data.message || 'Please try again.',
|
|
162
|
+
icon: 'lucide:circle-x',
|
|
163
|
+
color: 'error',
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
return
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
toast.add({
|
|
170
|
+
title: 'Admin account created',
|
|
171
|
+
description: 'Your admin account has been created successfully.',
|
|
172
|
+
icon: 'lucide:check-circle',
|
|
173
|
+
color: 'success',
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
stepper.value?.next()
|
|
177
|
+
} catch (error) {
|
|
178
|
+
if (import.meta.dev) {
|
|
179
|
+
console.error('Error creating admin account:', error)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
toast.add({
|
|
183
|
+
title: 'Error creating admin account',
|
|
184
|
+
description: 'Please check the console for more details.',
|
|
185
|
+
icon: 'lucide:circle-x',
|
|
186
|
+
color: 'error',
|
|
187
|
+
})
|
|
188
|
+
} finally {
|
|
189
|
+
isCreatingAdmin.value = false
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
114
193
|
function handleStepChange(step: number) {
|
|
194
|
+
if (step === 0) {
|
|
195
|
+
completeDatabaseSetup()
|
|
196
|
+
}
|
|
197
|
+
|
|
115
198
|
if (step === 1) {
|
|
116
|
-
|
|
199
|
+
createAdminAccount()
|
|
117
200
|
}
|
|
118
201
|
}
|
|
119
202
|
</script>
|
|
@@ -123,20 +206,14 @@ function handleStepChange(step: number) {
|
|
|
123
206
|
<div class="flex justify-center items-center h-screen">
|
|
124
207
|
<UCard class="w-[500px]">
|
|
125
208
|
<div class="flex flex-col gap-y-8">
|
|
126
|
-
<UStepper
|
|
127
|
-
ref="stepper"
|
|
128
|
-
v-model="currentStep"
|
|
129
|
-
:items="items"
|
|
130
|
-
disabled
|
|
131
|
-
linear
|
|
132
|
-
/>
|
|
209
|
+
<UStepper ref="stepper" v-model="currentStep" :items="items" linear />
|
|
133
210
|
|
|
134
211
|
<div>
|
|
135
212
|
<section v-if="currentStep === 0">
|
|
136
213
|
<UForm
|
|
137
214
|
id="database-setup-form"
|
|
138
215
|
class="flex flex-col gap-y-4"
|
|
139
|
-
@submit="handleStepChange(currentStep
|
|
216
|
+
@submit="handleStepChange(currentStep)"
|
|
140
217
|
>
|
|
141
218
|
<UFormField label="Supabase connection string" required>
|
|
142
219
|
<UInput
|
|
@@ -259,21 +336,85 @@ function handleStepChange(step: number) {
|
|
|
259
336
|
</UForm>
|
|
260
337
|
</section>
|
|
261
338
|
|
|
262
|
-
<section v-
|
|
339
|
+
<section v-if="currentStep === 1">
|
|
340
|
+
<UForm
|
|
341
|
+
id="admin-setup-form"
|
|
342
|
+
class="grid grid-cols-2 gap-4"
|
|
343
|
+
@submit="handleStepChange(currentStep)"
|
|
344
|
+
>
|
|
345
|
+
<UFormField label="Email" class="col-span-2" required>
|
|
346
|
+
<UInput
|
|
347
|
+
v-model="adminForm.email"
|
|
348
|
+
:disabled="isCreatingAdmin"
|
|
349
|
+
placeholder="you@example.com"
|
|
350
|
+
autofocus
|
|
351
|
+
required
|
|
352
|
+
/>
|
|
353
|
+
</UFormField>
|
|
354
|
+
|
|
355
|
+
<UFormField label="Username" required>
|
|
356
|
+
<UInput
|
|
357
|
+
v-model="adminForm.username"
|
|
358
|
+
:disabled="isCreatingAdmin"
|
|
359
|
+
placeholder="Username"
|
|
360
|
+
required
|
|
361
|
+
/>
|
|
362
|
+
</UFormField>
|
|
363
|
+
|
|
364
|
+
<UFormField label="Display Name" required>
|
|
365
|
+
<UInput
|
|
366
|
+
v-model="adminForm.display_name"
|
|
367
|
+
:disabled="isCreatingAdmin"
|
|
368
|
+
placeholder="Display Name"
|
|
369
|
+
required
|
|
370
|
+
/>
|
|
371
|
+
</UFormField>
|
|
372
|
+
|
|
373
|
+
<UFormField label="Password" class="col-span-2" required>
|
|
374
|
+
<UInput
|
|
375
|
+
v-model="adminForm.password"
|
|
376
|
+
:disabled="isCreatingAdmin"
|
|
377
|
+
type="password"
|
|
378
|
+
placeholder="• • • • "
|
|
379
|
+
required
|
|
380
|
+
/>
|
|
381
|
+
</UFormField>
|
|
382
|
+
</UForm>
|
|
383
|
+
</section>
|
|
384
|
+
|
|
385
|
+
<section v-else-if="currentStep === 2">
|
|
263
386
|
<div class="flex flex-col gap-y-4">
|
|
264
|
-
<
|
|
265
|
-
|
|
266
|
-
</h1>
|
|
387
|
+
<hgroup class="text-center">
|
|
388
|
+
<span class="text-3xl">🎉</span>
|
|
267
389
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
390
|
+
<h1 class="font-bold text-2xl">Setup Complete</h1>
|
|
391
|
+
</hgroup>
|
|
392
|
+
|
|
393
|
+
<p class="text-balance text-center text-lg">
|
|
394
|
+
Your database is ready and your admin account has been
|
|
395
|
+
created.
|
|
271
396
|
</p>
|
|
272
397
|
|
|
398
|
+
<UAlert icon="lucide:info" color="warning" variant="outline">
|
|
399
|
+
<template #description>
|
|
400
|
+
<p>
|
|
401
|
+
You need to confirm your email address before you can log
|
|
402
|
+
in. Please check your inbox and click on the confirmation
|
|
403
|
+
link. If you don't see the email, please check your spam
|
|
404
|
+
folder.
|
|
405
|
+
</p>
|
|
406
|
+
</template>
|
|
407
|
+
</UAlert>
|
|
408
|
+
|
|
273
409
|
<p class="text-center">
|
|
274
|
-
<
|
|
275
|
-
|
|
410
|
+
<UButton
|
|
411
|
+
icon="lucide:arrow-right"
|
|
412
|
+
to="/admin/login"
|
|
413
|
+
color="primary"
|
|
414
|
+
trailing
|
|
276
415
|
>
|
|
416
|
+
Go to login page
|
|
417
|
+
</UButton>
|
|
277
418
|
</p>
|
|
278
419
|
</div>
|
|
279
420
|
</section>
|
|
@@ -286,10 +427,9 @@ function handleStepChange(step: number) {
|
|
|
286
427
|
:form="currentStepId"
|
|
287
428
|
:loading="currentLoading"
|
|
288
429
|
leading-icon="lucide:check"
|
|
289
|
-
trailing-icon="lucide:arrow-right"
|
|
290
430
|
type="submit"
|
|
291
431
|
>
|
|
292
|
-
|
|
432
|
+
{{ currentStepSubmitLabel }}
|
|
293
433
|
</UButton>
|
|
294
434
|
</div>
|
|
295
435
|
</div>
|
package/app/pages/admin.vue
CHANGED
|
@@ -14,6 +14,7 @@ watch(visibility, async (current, previous) => {
|
|
|
14
14
|
if (current === 'visible' && previous === 'hidden') {
|
|
15
15
|
if (
|
|
16
16
|
!isLoggedIn.value &&
|
|
17
|
+
!route.path.startsWith('/admin/setup') &&
|
|
17
18
|
route.path.startsWith('/admin/') &&
|
|
18
19
|
route.path !== '/admin/login' &&
|
|
19
20
|
route.path !== '/admin/signup'
|
package/package.json
CHANGED
package/public/schema.sql
CHANGED
|
@@ -117,13 +117,30 @@ begin
|
|
|
117
117
|
end
|
|
118
118
|
$$;
|
|
119
119
|
|
|
120
|
+
-- Allow update access to healthcheck for authenticated users and dashboard_user
|
|
121
|
+
do $$
|
|
122
|
+
begin
|
|
123
|
+
if not exists (
|
|
124
|
+
select 1 from pg_policies where tablename = 'healthcheck' and policyname = 'Enable update for authenticated users on healthcheck'
|
|
125
|
+
) then
|
|
126
|
+
create policy "Enable update for authenticated users on healthcheck"
|
|
127
|
+
on public.healthcheck
|
|
128
|
+
for update
|
|
129
|
+
to authenticated, dashboard_user
|
|
130
|
+
using (true)
|
|
131
|
+
with check (true);
|
|
132
|
+
end if;
|
|
133
|
+
end
|
|
134
|
+
$$;
|
|
135
|
+
|
|
120
136
|
-- Profiles
|
|
121
137
|
create table if not exists public.profiles (
|
|
122
138
|
id uuid references auth.users on delete cascade not null primary key,
|
|
123
139
|
updated_at timestamp with time zone,
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
140
|
+
username text,
|
|
141
|
+
display_name text,
|
|
142
|
+
is_admin boolean not null default false,
|
|
143
|
+
constraint username_length check (char_length(username) >= 3)
|
|
127
144
|
);
|
|
128
145
|
|
|
129
146
|
alter table public.profiles enable row level security;
|
|
@@ -149,8 +166,8 @@ language plpgsql
|
|
|
149
166
|
security definer set search_path = ''
|
|
150
167
|
as $$
|
|
151
168
|
begin
|
|
152
|
-
insert into public.profiles (id,
|
|
153
|
-
values (new.id, new.raw_user_meta_data ->> '
|
|
169
|
+
insert into public.profiles (id, username, display_name)
|
|
170
|
+
values (new.id, new.raw_user_meta_data ->> 'username', new.raw_user_meta_data ->> 'display_name');
|
|
154
171
|
return new;
|
|
155
172
|
end;
|
|
156
173
|
$$;
|
|
@@ -118,13 +118,6 @@ export default defineEventHandler(async (event) => {
|
|
|
118
118
|
await sql.unsafe(statement)
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
-
// Set the first_setup flag in the healthcheck table to false
|
|
122
|
-
await sql`
|
|
123
|
-
UPDATE healthcheck
|
|
124
|
-
SET config_value = 'false'
|
|
125
|
-
WHERE config_name = 'first_setup';
|
|
126
|
-
`
|
|
127
|
-
|
|
128
121
|
return {
|
|
129
122
|
success: true,
|
|
130
123
|
message: 'Database setup completed successfully.',
|
|
@@ -1,28 +1,55 @@
|
|
|
1
1
|
import { serverSupabaseClient } from '#supabase/server'
|
|
2
2
|
|
|
3
|
+
interface Payload {
|
|
4
|
+
username: string
|
|
5
|
+
display_name: string
|
|
6
|
+
email: string
|
|
7
|
+
password: string
|
|
8
|
+
is_admin?: boolean
|
|
9
|
+
}
|
|
10
|
+
|
|
3
11
|
export default defineEventHandler(async (event) => {
|
|
4
12
|
const client = await serverSupabaseClient<Database>(event)
|
|
5
13
|
|
|
6
|
-
const body = await readBody(event)
|
|
14
|
+
const body = await readBody<Payload>(event)
|
|
7
15
|
|
|
8
16
|
const { data, error } = await client.auth.signUp({
|
|
9
17
|
email: body.email,
|
|
10
18
|
password: body.password,
|
|
11
19
|
options: {
|
|
12
20
|
data: {
|
|
13
|
-
|
|
14
|
-
|
|
21
|
+
username: body.username,
|
|
22
|
+
display_name: body.display_name,
|
|
23
|
+
is_admin: body.is_admin || false,
|
|
15
24
|
},
|
|
16
25
|
},
|
|
17
26
|
})
|
|
18
27
|
|
|
19
28
|
if (error) {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
cause: error.cause,
|
|
29
|
+
return {
|
|
30
|
+
success: false,
|
|
23
31
|
message: error.message,
|
|
24
|
-
|
|
32
|
+
error,
|
|
33
|
+
}
|
|
25
34
|
}
|
|
26
35
|
|
|
27
|
-
|
|
36
|
+
// Set the first_setup flag in the healthcheck table to false
|
|
37
|
+
const { error: healthcheckError } = await client
|
|
38
|
+
.from('healthcheck')
|
|
39
|
+
.update({ config_value: 'false' })
|
|
40
|
+
.eq('config_name', 'first_setup')
|
|
41
|
+
|
|
42
|
+
if (healthcheckError) {
|
|
43
|
+
return {
|
|
44
|
+
success: false,
|
|
45
|
+
message: healthcheckError.message,
|
|
46
|
+
error: healthcheckError,
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
success: true,
|
|
52
|
+
message: 'User registered successfully',
|
|
53
|
+
data,
|
|
54
|
+
}
|
|
28
55
|
})
|