@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.
@@ -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({ route: to })
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
- async function completeSetup() {
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
- completeSetup()
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 + 1)"
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-else-if="currentStep === 1">
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
- <h1 class="text-2xl font-bold text-center">
265
- Setup Complete 🎉
266
- </h1>
387
+ <hgroup class="text-center">
388
+ <span class="text-3xl">🎉</span>
267
389
 
268
- <p>
269
- Your database has been set up successfully! 🎉 You can now log
270
- in to the admin panel using your Supabase credentials.
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
- <ULink to="/admin/login" class="underline">
275
- Go to login page</ULink
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
- Submit and continue
432
+ {{ currentStepSubmitLabel }}
293
433
  </UButton>
294
434
  </div>
295
435
  </div>
@@ -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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@plutocms/supabase",
3
3
  "type": "module",
4
- "version": "0.0.1-alpha.8",
4
+ "version": "0.0.1-alpha.9",
5
5
  "main": "./nuxt.config.ts",
6
6
  "scripts": {
7
7
  "build": "nuxt build",
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
- first_name text,
125
- last_name text,
126
- constraint first_name_length check (char_length(first_name) >= 3)
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, first_name, last_name)
153
- values (new.id, new.raw_user_meta_data ->> 'first_name', new.raw_user_meta_data ->> 'last_name');
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
- first_name: body.first_name,
14
- last_name: body.last_name,
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
- throw createError({
21
- statusCode: Number.parseInt(error.code ?? '500'),
22
- cause: error.cause,
29
+ return {
30
+ success: false,
23
31
  message: error.message,
24
- })
32
+ error,
33
+ }
25
34
  }
26
35
 
27
- return { message: 'User registered successfully', data }
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
  })