@plutocms/supabase 0.0.1-alpha.8 → 0.1.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/CHANGELOG.md +98 -0
- package/README.md +2 -4
- package/app/assets/css/tailwind.css +5 -0
- package/app/components/navbar/NavbarAdmin.vue +6 -5
- package/app/composables/auth.ts +124 -2
- package/app/middleware/auth.ts +13 -6
- package/app/middleware/setup-check.ts +25 -21
- package/app/pages/admin/(auth)/confirm.vue +38 -0
- package/app/pages/admin/(auth)/forgot-password.vue +88 -0
- package/app/pages/admin/(auth)/login.vue +5 -1
- package/app/pages/admin/(auth)/signup.vue +76 -16
- package/app/pages/admin/(auth)/update-password.vue +134 -0
- package/app/pages/admin/setup.vue +31 -28
- package/app/pages/admin.vue +3 -4
- package/modules/pluto-migrations.ts +121 -0
- package/nuxt.config.ts +14 -1
- package/package.json +40 -24
- package/public/schema.sql +66 -8
- package/scripts/supabase-typegen.ts +36 -21
- package/scripts/tsconfig.json +18 -0
- package/server/api/auth/signup.post.ts +46 -0
- package/server/api/setup/create.post.ts +38 -88
- package/server/plugins/migrations.ts +60 -0
- package/server/utils/migrations.ts +88 -0
- package/server/utils/sql.ts +81 -0
- package/server/utils/typegen.ts +68 -0
- package/shared/types/runtime-config.d.ts +16 -0
- package/shared/types/supabase.ts +90 -117
- package/.agents/skills/nuxt/GENERATION.md +0 -5
- package/.agents/skills/nuxt/SKILL.md +0 -55
- package/.agents/skills/nuxt/references/advanced-hooks.md +0 -289
- package/.agents/skills/nuxt/references/advanced-layers.md +0 -299
- package/.agents/skills/nuxt/references/advanced-module-authoring.md +0 -554
- package/.agents/skills/nuxt/references/best-practices-data-fetching.md +0 -357
- package/.agents/skills/nuxt/references/best-practices-ssr.md +0 -355
- package/.agents/skills/nuxt/references/core-cli.md +0 -263
- package/.agents/skills/nuxt/references/core-config.md +0 -162
- package/.agents/skills/nuxt/references/core-data-fetching.md +0 -236
- package/.agents/skills/nuxt/references/core-deployment.md +0 -224
- package/.agents/skills/nuxt/references/core-directory-structure.md +0 -269
- package/.agents/skills/nuxt/references/core-modules.md +0 -292
- package/.agents/skills/nuxt/references/core-routing.md +0 -226
- package/.agents/skills/nuxt/references/features-components-autoimport.md +0 -328
- package/.agents/skills/nuxt/references/features-components.md +0 -264
- package/.agents/skills/nuxt/references/features-composables.md +0 -276
- package/.agents/skills/nuxt/references/features-server.md +0 -265
- package/.agents/skills/nuxt/references/features-state.md +0 -194
- package/.agents/skills/nuxt/references/rendering-modes.md +0 -237
- package/.agents/skills/nuxt-ui/SKILL.md +0 -334
- package/.agents/skills/nuxt-ui/references/components.md +0 -377
- package/.agents/skills/nuxt-ui/references/composables.md +0 -127
- package/.agents/skills/nuxt-ui/references/layouts/chat.md +0 -195
- package/.agents/skills/nuxt-ui/references/layouts/dashboard.md +0 -220
- package/.agents/skills/nuxt-ui/references/layouts/docs.md +0 -141
- package/.agents/skills/nuxt-ui/references/layouts/editor.md +0 -167
- package/.agents/skills/nuxt-ui/references/layouts/page.md +0 -260
- package/.agents/skills/nuxt-ui/references/theming.md +0 -427
- package/.agents/skills/supabase-postgres-best-practices/AGENTS.md +0 -68
- package/.agents/skills/supabase-postgres-best-practices/CLAUDE.md +0 -1
- package/.agents/skills/supabase-postgres-best-practices/README.md +0 -116
- package/.agents/skills/supabase-postgres-best-practices/SKILL.md +0 -64
- package/.agents/skills/supabase-postgres-best-practices/references/advanced-full-text-search.md +0 -55
- package/.agents/skills/supabase-postgres-best-practices/references/advanced-jsonb-indexing.md +0 -49
- package/.agents/skills/supabase-postgres-best-practices/references/conn-idle-timeout.md +0 -46
- package/.agents/skills/supabase-postgres-best-practices/references/conn-limits.md +0 -44
- package/.agents/skills/supabase-postgres-best-practices/references/conn-pooling.md +0 -41
- package/.agents/skills/supabase-postgres-best-practices/references/conn-prepared-statements.md +0 -46
- package/.agents/skills/supabase-postgres-best-practices/references/data-batch-inserts.md +0 -54
- package/.agents/skills/supabase-postgres-best-practices/references/data-n-plus-one.md +0 -53
- package/.agents/skills/supabase-postgres-best-practices/references/data-pagination.md +0 -50
- package/.agents/skills/supabase-postgres-best-practices/references/data-upsert.md +0 -50
- package/.agents/skills/supabase-postgres-best-practices/references/lock-advisory.md +0 -56
- package/.agents/skills/supabase-postgres-best-practices/references/lock-deadlock-prevention.md +0 -68
- package/.agents/skills/supabase-postgres-best-practices/references/lock-short-transactions.md +0 -50
- package/.agents/skills/supabase-postgres-best-practices/references/lock-skip-locked.md +0 -54
- package/.agents/skills/supabase-postgres-best-practices/references/monitor-explain-analyze.md +0 -45
- package/.agents/skills/supabase-postgres-best-practices/references/monitor-pg-stat-statements.md +0 -55
- package/.agents/skills/supabase-postgres-best-practices/references/monitor-vacuum-analyze.md +0 -55
- package/.agents/skills/supabase-postgres-best-practices/references/query-composite-indexes.md +0 -44
- package/.agents/skills/supabase-postgres-best-practices/references/query-covering-indexes.md +0 -40
- package/.agents/skills/supabase-postgres-best-practices/references/query-index-types.md +0 -48
- package/.agents/skills/supabase-postgres-best-practices/references/query-missing-indexes.md +0 -43
- package/.agents/skills/supabase-postgres-best-practices/references/query-partial-indexes.md +0 -45
- package/.agents/skills/supabase-postgres-best-practices/references/schema-constraints.md +0 -80
- package/.agents/skills/supabase-postgres-best-practices/references/schema-data-types.md +0 -46
- package/.agents/skills/supabase-postgres-best-practices/references/schema-foreign-key-indexes.md +0 -59
- package/.agents/skills/supabase-postgres-best-practices/references/schema-lowercase-identifiers.md +0 -55
- package/.agents/skills/supabase-postgres-best-practices/references/schema-partitioning.md +0 -55
- package/.agents/skills/supabase-postgres-best-practices/references/schema-primary-keys.md +0 -61
- package/.agents/skills/supabase-postgres-best-practices/references/security-privileges.md +0 -54
- package/.agents/skills/supabase-postgres-best-practices/references/security-rls-basics.md +0 -50
- package/.agents/skills/supabase-postgres-best-practices/references/security-rls-performance.md +0 -57
- package/.editorconfig +0 -12
- package/.eslintignore +0 -1
- package/.prettierrc +0 -9
- package/.vscode/extensions.json +0 -10
- package/.vscode/settings.json +0 -61
- package/bun.lock +0 -2837
- package/eslint.config.mjs +0 -88
- package/server/api/category/list.get.ts +0 -9
- package/server/api/category/new.post.ts +0 -31
- package/server/api/signup.post.ts +0 -28
- package/skills-lock.json +0 -20
|
@@ -7,10 +7,12 @@ useHead({
|
|
|
7
7
|
title: 'Sign Up',
|
|
8
8
|
})
|
|
9
9
|
|
|
10
|
+
const toast = useToast()
|
|
11
|
+
|
|
10
12
|
const form = ref({
|
|
11
13
|
email: '',
|
|
12
|
-
|
|
13
|
-
|
|
14
|
+
username: '',
|
|
15
|
+
display_name: '',
|
|
14
16
|
password: '',
|
|
15
17
|
confirm_password: '',
|
|
16
18
|
})
|
|
@@ -21,26 +23,44 @@ const passwordMatch = computed(() => {
|
|
|
21
23
|
return form.value.password === form.value.confirm_password
|
|
22
24
|
})
|
|
23
25
|
|
|
26
|
+
const isEmailVerificationMessageVisible = ref<boolean>(false)
|
|
27
|
+
|
|
24
28
|
async function submitForm() {
|
|
25
29
|
if (!passwordMatch.value) {
|
|
26
30
|
return
|
|
27
31
|
}
|
|
28
32
|
|
|
29
33
|
isSubmitting.value = true
|
|
34
|
+
|
|
30
35
|
try {
|
|
31
|
-
await $fetch('/api/signup', {
|
|
36
|
+
await $fetch('/api/auth/signup', {
|
|
32
37
|
method: 'POST',
|
|
33
38
|
body: {
|
|
34
39
|
email: form.value.email,
|
|
35
|
-
|
|
36
|
-
|
|
40
|
+
username: form.value.username,
|
|
41
|
+
display_name: form.value.display_name,
|
|
37
42
|
password: form.value.password,
|
|
38
43
|
},
|
|
39
44
|
})
|
|
40
45
|
|
|
41
|
-
|
|
46
|
+
toast.add({
|
|
47
|
+
title: 'Account created successfully',
|
|
48
|
+
description: 'You still need to verify your email before you can log in.',
|
|
49
|
+
icon: 'lucide:check',
|
|
50
|
+
color: 'success',
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
isEmailVerificationMessageVisible.value = true
|
|
42
54
|
} catch (error) {
|
|
43
55
|
console.error('Error signing up:', error)
|
|
56
|
+
|
|
57
|
+
toast.add({
|
|
58
|
+
title: 'Could not create account',
|
|
59
|
+
description:
|
|
60
|
+
error instanceof Error ? error.message : 'An unknown error occurred.',
|
|
61
|
+
icon: 'lucide:circle-x',
|
|
62
|
+
color: 'error',
|
|
63
|
+
})
|
|
44
64
|
} finally {
|
|
45
65
|
isSubmitting.value = false
|
|
46
66
|
}
|
|
@@ -50,52 +70,92 @@ async function submitForm() {
|
|
|
50
70
|
<template>
|
|
51
71
|
<div class="grid h-full place-items-center">
|
|
52
72
|
<UCard class="w-100">
|
|
53
|
-
<
|
|
73
|
+
<div v-if="isEmailVerificationMessageVisible" class="space-y-6 py-10">
|
|
74
|
+
<div class="text-center space-y-4">
|
|
75
|
+
<hgroup class="flex flex-col gap-y-1">
|
|
76
|
+
<span class="text-5xl">
|
|
77
|
+
<UIcon name="lucide:mail-warning" class="inline-block" />
|
|
78
|
+
</span>
|
|
79
|
+
|
|
80
|
+
<h1 class="text-3xl font-bold">Almost there!</h1>
|
|
81
|
+
</hgroup>
|
|
82
|
+
|
|
83
|
+
<p>
|
|
84
|
+
A verification link has been sent to your email. Please check your
|
|
85
|
+
inbox and follow the instructions to verify your account.
|
|
86
|
+
</p>
|
|
87
|
+
|
|
88
|
+
<UAlert icon="lucide:check" color="success" variant="outline">
|
|
89
|
+
<template #description>
|
|
90
|
+
You can close this page. You will be logged in automatically once
|
|
91
|
+
you verify your email.
|
|
92
|
+
</template>
|
|
93
|
+
</UAlert>
|
|
94
|
+
|
|
95
|
+
<UAlert
|
|
96
|
+
icon="lucide:alert-triangle"
|
|
97
|
+
color="warning"
|
|
98
|
+
variant="outline"
|
|
99
|
+
>
|
|
100
|
+
<template #description>
|
|
101
|
+
If you don't see the email, please check your spam folder.
|
|
102
|
+
</template>
|
|
103
|
+
</UAlert>
|
|
104
|
+
</div>
|
|
105
|
+
</div>
|
|
106
|
+
|
|
107
|
+
<form v-else @submit.prevent="submitForm">
|
|
54
108
|
<div class="flex flex-col gap-y-6">
|
|
55
109
|
<h1 class="text-3xl font-bold">Create Account</h1>
|
|
56
110
|
|
|
57
|
-
<UFormField label="Email">
|
|
111
|
+
<UFormField label="Email" required>
|
|
58
112
|
<UInput
|
|
59
113
|
v-model="form.email"
|
|
60
114
|
type="email"
|
|
61
115
|
placeholder="victor@example.com"
|
|
62
116
|
class="w-full"
|
|
117
|
+
required
|
|
63
118
|
/>
|
|
64
119
|
</UFormField>
|
|
65
120
|
|
|
66
121
|
<div class="flex gap-x-4">
|
|
67
|
-
<UFormField label="
|
|
122
|
+
<UFormField label="Username" required>
|
|
68
123
|
<UInput
|
|
69
|
-
v-model="form.
|
|
70
|
-
placeholder="e.g:
|
|
124
|
+
v-model="form.username"
|
|
125
|
+
placeholder="e.g: johndoe"
|
|
71
126
|
class="w-full"
|
|
127
|
+
minlength="3"
|
|
128
|
+
required
|
|
72
129
|
/>
|
|
73
130
|
</UFormField>
|
|
74
131
|
|
|
75
|
-
<UFormField label="
|
|
132
|
+
<UFormField label="Display Name" required>
|
|
76
133
|
<UInput
|
|
77
|
-
v-model="form.
|
|
78
|
-
placeholder="e.g: Doe"
|
|
134
|
+
v-model="form.display_name"
|
|
135
|
+
placeholder="e.g: John Doe"
|
|
79
136
|
class="w-full"
|
|
137
|
+
required
|
|
80
138
|
/>
|
|
81
139
|
</UFormField>
|
|
82
140
|
</div>
|
|
83
141
|
|
|
84
|
-
<UFormField :error="!passwordMatch" label="Password">
|
|
142
|
+
<UFormField :error="!passwordMatch" label="Password" required>
|
|
85
143
|
<UInput
|
|
86
144
|
v-model="form.password"
|
|
87
145
|
placeholder="••••"
|
|
88
146
|
class="w-full"
|
|
89
147
|
type="password"
|
|
148
|
+
required
|
|
90
149
|
/>
|
|
91
150
|
</UFormField>
|
|
92
151
|
|
|
93
|
-
<UFormField :error="!passwordMatch" label="Confirm Password">
|
|
152
|
+
<UFormField :error="!passwordMatch" label="Confirm Password" required>
|
|
94
153
|
<UInput
|
|
95
154
|
v-model="form.confirm_password"
|
|
96
155
|
placeholder="••••"
|
|
97
156
|
class="w-full"
|
|
98
157
|
type="password"
|
|
158
|
+
required
|
|
99
159
|
/>
|
|
100
160
|
</UFormField>
|
|
101
161
|
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
definePageMeta({
|
|
3
|
+
layout: 'auth',
|
|
4
|
+
})
|
|
5
|
+
|
|
6
|
+
useHead({
|
|
7
|
+
title: 'Update Password',
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
const route = useRoute()
|
|
11
|
+
const supabase = useSupabaseClient()
|
|
12
|
+
|
|
13
|
+
// Exchange the PKCE code for a session when arriving from a password reset email
|
|
14
|
+
onMounted(async () => {
|
|
15
|
+
const code = route.query.code as string | undefined
|
|
16
|
+
|
|
17
|
+
if (code) {
|
|
18
|
+
await supabase.auth.exchangeCodeForSession(code)
|
|
19
|
+
}
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
const { isSubmitting, updatePassword } = await useAuth()
|
|
23
|
+
|
|
24
|
+
const form = ref({
|
|
25
|
+
password: '',
|
|
26
|
+
confirm_password: '',
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
const passwordMatch = computed(() => {
|
|
30
|
+
return form.value.password === form.value.confirm_password
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
const isPasswordUpdated = ref(false)
|
|
34
|
+
|
|
35
|
+
async function submitForm() {
|
|
36
|
+
if (!passwordMatch.value) {
|
|
37
|
+
return
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const result = await updatePassword(form.value.password)
|
|
41
|
+
|
|
42
|
+
if (result.success) {
|
|
43
|
+
isPasswordUpdated.value = true
|
|
44
|
+
|
|
45
|
+
await supabase.auth.signOut()
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
</script>
|
|
49
|
+
|
|
50
|
+
<template>
|
|
51
|
+
<div class="grid h-full place-items-center">
|
|
52
|
+
<UCard class="w-100">
|
|
53
|
+
<div v-if="isPasswordUpdated" class="space-y-6 py-10">
|
|
54
|
+
<div class="text-center space-y-4">
|
|
55
|
+
<hgroup class="flex flex-col gap-y-1">
|
|
56
|
+
<span class="text-5xl">
|
|
57
|
+
<UIcon name="lucide:lock-keyhole" class="inline-block" />
|
|
58
|
+
</span>
|
|
59
|
+
|
|
60
|
+
<h1 class="text-3xl font-bold">Password Updated!</h1>
|
|
61
|
+
</hgroup>
|
|
62
|
+
|
|
63
|
+
<p>
|
|
64
|
+
Your password has been changed. You can now log in with your new
|
|
65
|
+
password.
|
|
66
|
+
</p>
|
|
67
|
+
|
|
68
|
+
<div class="flex items-center justify-center">
|
|
69
|
+
<UButton to="/admin/login" color="primary" icon="lucide:log-in">
|
|
70
|
+
Go to Login
|
|
71
|
+
</UButton>
|
|
72
|
+
</div>
|
|
73
|
+
</div>
|
|
74
|
+
</div>
|
|
75
|
+
|
|
76
|
+
<form v-else @submit.prevent="submitForm">
|
|
77
|
+
<div class="flex flex-col gap-y-6">
|
|
78
|
+
<hgroup class="flex flex-col gap-y-1">
|
|
79
|
+
<h1 class="text-3xl font-bold">Update Password</h1>
|
|
80
|
+
<p class="text-sm text-muted">Enter your new password below.</p>
|
|
81
|
+
</hgroup>
|
|
82
|
+
|
|
83
|
+
<UFormField label="New Password">
|
|
84
|
+
<UInput
|
|
85
|
+
v-model="form.password"
|
|
86
|
+
placeholder="••••"
|
|
87
|
+
type="password"
|
|
88
|
+
class="w-full"
|
|
89
|
+
autocomplete="new-password"
|
|
90
|
+
required
|
|
91
|
+
/>
|
|
92
|
+
</UFormField>
|
|
93
|
+
|
|
94
|
+
<UFormField
|
|
95
|
+
:error="
|
|
96
|
+
form.confirm_password && !passwordMatch
|
|
97
|
+
? 'Passwords do not match.'
|
|
98
|
+
: undefined
|
|
99
|
+
"
|
|
100
|
+
label="Confirm New Password"
|
|
101
|
+
>
|
|
102
|
+
<UInput
|
|
103
|
+
v-model="form.confirm_password"
|
|
104
|
+
placeholder="••••"
|
|
105
|
+
type="password"
|
|
106
|
+
class="w-full"
|
|
107
|
+
autocomplete="new-password"
|
|
108
|
+
required
|
|
109
|
+
/>
|
|
110
|
+
</UFormField>
|
|
111
|
+
|
|
112
|
+
<div class="flex items-center justify-between gap-x-4">
|
|
113
|
+
<UFormField>
|
|
114
|
+
<UButton
|
|
115
|
+
:loading="isSubmitting"
|
|
116
|
+
:disabled="!passwordMatch"
|
|
117
|
+
type="submit"
|
|
118
|
+
icon="lucide:lock-keyhole"
|
|
119
|
+
>
|
|
120
|
+
Update Password
|
|
121
|
+
</UButton>
|
|
122
|
+
</UFormField>
|
|
123
|
+
|
|
124
|
+
<UFormField>
|
|
125
|
+
<UButton variant="link" to="/admin/login" class="px-0">
|
|
126
|
+
Back to Login
|
|
127
|
+
</UButton>
|
|
128
|
+
</UFormField>
|
|
129
|
+
</div>
|
|
130
|
+
</div>
|
|
131
|
+
</form>
|
|
132
|
+
</UCard>
|
|
133
|
+
</div>
|
|
134
|
+
</template>
|
|
@@ -23,11 +23,6 @@ const items = ref<StepperItem[]>([
|
|
|
23
23
|
icon: 'i-lucide-database',
|
|
24
24
|
},
|
|
25
25
|
|
|
26
|
-
/* {
|
|
27
|
-
title: 'Admin Account',
|
|
28
|
-
icon: 'i-lucide-user',
|
|
29
|
-
}, */
|
|
30
|
-
|
|
31
26
|
{
|
|
32
27
|
title: 'Complete Setup',
|
|
33
28
|
icon: 'i-lucide-check',
|
|
@@ -61,7 +56,15 @@ const currentStepId = computed(() => {
|
|
|
61
56
|
return undefined
|
|
62
57
|
})
|
|
63
58
|
|
|
64
|
-
|
|
59
|
+
const currentStepSubmitLabel = computed(() => {
|
|
60
|
+
if (currentStep.value === 0) {
|
|
61
|
+
return 'Set up database'
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return 'Submit'
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
async function completeDatabaseSetup() {
|
|
65
68
|
try {
|
|
66
69
|
isSettingUpDatabase.value = true
|
|
67
70
|
|
|
@@ -112,8 +115,8 @@ async function completeSetup() {
|
|
|
112
115
|
}
|
|
113
116
|
|
|
114
117
|
function handleStepChange(step: number) {
|
|
115
|
-
if (step ===
|
|
116
|
-
|
|
118
|
+
if (step === 0) {
|
|
119
|
+
completeDatabaseSetup()
|
|
117
120
|
}
|
|
118
121
|
}
|
|
119
122
|
</script>
|
|
@@ -121,22 +124,16 @@ function handleStepChange(step: number) {
|
|
|
121
124
|
<template>
|
|
122
125
|
<UApp>
|
|
123
126
|
<div class="flex justify-center items-center h-screen">
|
|
124
|
-
<UCard class="w-
|
|
127
|
+
<UCard class="w-125">
|
|
125
128
|
<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
|
-
/>
|
|
129
|
+
<UStepper ref="stepper" v-model="currentStep" :items="items" linear />
|
|
133
130
|
|
|
134
131
|
<div>
|
|
135
132
|
<section v-if="currentStep === 0">
|
|
136
133
|
<UForm
|
|
137
134
|
id="database-setup-form"
|
|
138
135
|
class="flex flex-col gap-y-4"
|
|
139
|
-
@submit="handleStepChange(currentStep
|
|
136
|
+
@submit="handleStepChange(currentStep)"
|
|
140
137
|
>
|
|
141
138
|
<UFormField label="Supabase connection string" required>
|
|
142
139
|
<UInput
|
|
@@ -259,21 +256,28 @@ function handleStepChange(step: number) {
|
|
|
259
256
|
</UForm>
|
|
260
257
|
</section>
|
|
261
258
|
|
|
262
|
-
<section v-
|
|
259
|
+
<section v-if="currentStep === 1">
|
|
263
260
|
<div class="flex flex-col gap-y-4">
|
|
264
|
-
<
|
|
265
|
-
|
|
266
|
-
|
|
261
|
+
<hgroup class="text-center">
|
|
262
|
+
<span class="text-3xl">🎉</span>
|
|
263
|
+
|
|
264
|
+
<h1 class="font-bold text-2xl">Setup Complete</h1>
|
|
265
|
+
</hgroup>
|
|
267
266
|
|
|
268
|
-
<p>
|
|
269
|
-
Your database
|
|
270
|
-
|
|
267
|
+
<p class="text-balance text-center text-lg">
|
|
268
|
+
Your database is ready. Create your admin account by signing
|
|
269
|
+
up.
|
|
271
270
|
</p>
|
|
272
271
|
|
|
273
272
|
<p class="text-center">
|
|
274
|
-
<
|
|
275
|
-
|
|
273
|
+
<UButton
|
|
274
|
+
icon="lucide:arrow-right"
|
|
275
|
+
to="/admin/signup"
|
|
276
|
+
color="primary"
|
|
277
|
+
trailing
|
|
276
278
|
>
|
|
279
|
+
Go to sign up
|
|
280
|
+
</UButton>
|
|
277
281
|
</p>
|
|
278
282
|
</div>
|
|
279
283
|
</section>
|
|
@@ -286,10 +290,9 @@ function handleStepChange(step: number) {
|
|
|
286
290
|
:form="currentStepId"
|
|
287
291
|
:loading="currentLoading"
|
|
288
292
|
leading-icon="lucide:check"
|
|
289
|
-
trailing-icon="lucide:arrow-right"
|
|
290
293
|
type="submit"
|
|
291
294
|
>
|
|
292
|
-
|
|
295
|
+
{{ currentStepSubmitLabel }}
|
|
293
296
|
</UButton>
|
|
294
297
|
</div>
|
|
295
298
|
</div>
|
package/app/pages/admin.vue
CHANGED
|
@@ -5,7 +5,7 @@ definePageMeta({
|
|
|
5
5
|
})
|
|
6
6
|
|
|
7
7
|
const route = useRoute()
|
|
8
|
-
const { isLoggedIn, logout } = await useAuth()
|
|
8
|
+
const { isLoggedIn, logout, allowedUnauthenticatedPaths } = await useAuth()
|
|
9
9
|
const toast = useToast()
|
|
10
10
|
|
|
11
11
|
const visibility = useDocumentVisibility()
|
|
@@ -14,9 +14,8 @@ watch(visibility, async (current, previous) => {
|
|
|
14
14
|
if (current === 'visible' && previous === 'hidden') {
|
|
15
15
|
if (
|
|
16
16
|
!isLoggedIn.value &&
|
|
17
|
-
route.path.startsWith('/admin/') &&
|
|
18
|
-
route.path
|
|
19
|
-
route.path !== '/admin/signup'
|
|
17
|
+
!route.path.startsWith('/admin/setup') &&
|
|
18
|
+
!allowedUnauthenticatedPaths.includes(route.path)
|
|
20
19
|
) {
|
|
21
20
|
await logout({ redirectTo: route.path })
|
|
22
21
|
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs'
|
|
2
|
+
import { dirname, join, relative } from 'node:path'
|
|
3
|
+
import { createResolver, defineNuxtModule, getLayerDirectories } from 'nuxt/kit'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Discovers schema.[layerName].sql files across all Nuxt layers,
|
|
7
|
+
* reads their content at build time, and populates runtimeConfig
|
|
8
|
+
* so the migrations plugin can run them at server startup.
|
|
9
|
+
*
|
|
10
|
+
* Also seeds the consuming app's shared/types/supabase.ts with a
|
|
11
|
+
* re-export of this layer's own committed snapshot when the consumer
|
|
12
|
+
* has none yet, so `Database` resolves to real types instead of
|
|
13
|
+
* `unknown` on first run. A re-export (rather than a full copy) keeps
|
|
14
|
+
* a single canonical `Database` declaration so Nuxt's shared-imports
|
|
15
|
+
* scanning across layers doesn't see two conflicting definitions. The
|
|
16
|
+
* runtime migration plugin overwrites this with a fully generated file
|
|
17
|
+
* once real migrations run (see server/plugins/migrations.ts).
|
|
18
|
+
*/
|
|
19
|
+
export default defineNuxtModule({
|
|
20
|
+
meta: {
|
|
21
|
+
name: 'pluto-migrations',
|
|
22
|
+
configKey: 'plutoMigrations',
|
|
23
|
+
},
|
|
24
|
+
|
|
25
|
+
setup(_options, nuxt) {
|
|
26
|
+
// Let the runtime migration plugin know where the consumer's
|
|
27
|
+
// project root is, so it can write the generated types file there.
|
|
28
|
+
nuxt.options.runtimeConfig.plutoRootDir = nuxt.options.rootDir
|
|
29
|
+
|
|
30
|
+
const consumerTypesPath = join(
|
|
31
|
+
nuxt.options.rootDir,
|
|
32
|
+
'shared/types/supabase.ts'
|
|
33
|
+
)
|
|
34
|
+
if (!existsSync(consumerTypesPath)) {
|
|
35
|
+
// Resolve *this exact* @plutocms/supabase instance's own snapshot
|
|
36
|
+
// (local-linked or npm-installed, whichever is actually running)
|
|
37
|
+
// rather than a bare package specifier, which would re-resolve
|
|
38
|
+
// through the consumer's own node_modules and could pick up a
|
|
39
|
+
// different, possibly stale, copy of the package.
|
|
40
|
+
const { resolve } = createResolver(import.meta.url)
|
|
41
|
+
const ownTypesPath = resolve('../shared/types/supabase.ts')
|
|
42
|
+
const consumerTypesDir = dirname(consumerTypesPath)
|
|
43
|
+
let importSpecifier = relative(consumerTypesDir, ownTypesPath)
|
|
44
|
+
.replace(/\\/g, '/')
|
|
45
|
+
.replace(/\.ts$/, '')
|
|
46
|
+
if (!importSpecifier.startsWith('.')) {
|
|
47
|
+
importSpecifier = `./${importSpecifier}`
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
mkdirSync(consumerTypesDir, { recursive: true })
|
|
51
|
+
writeFileSync(
|
|
52
|
+
consumerTypesPath,
|
|
53
|
+
`export * from '${importSpecifier}'\n`,
|
|
54
|
+
'utf-8'
|
|
55
|
+
)
|
|
56
|
+
console.warn(
|
|
57
|
+
'[pluto-migrations] Seeded shared/types/supabase.ts (re-exporting @plutocms/supabase types; no generated types found yet).'
|
|
58
|
+
)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const layerDirs = getLayerDirectories()
|
|
62
|
+
const layerSchemas: Record<string, string> = {}
|
|
63
|
+
const schemaPattern = /^schema\.(.+)\.sql$/
|
|
64
|
+
|
|
65
|
+
for (const layer of layerDirs) {
|
|
66
|
+
// Each layer may have a public/ directory with schema files
|
|
67
|
+
const publicDir = join(layer.root, 'public')
|
|
68
|
+
|
|
69
|
+
if (!existsSync(publicDir)) {
|
|
70
|
+
continue
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
let files: string[] = []
|
|
74
|
+
try {
|
|
75
|
+
files = readdirSync(publicDir, 'utf-8')
|
|
76
|
+
} catch {
|
|
77
|
+
continue
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
for (const file of files) {
|
|
81
|
+
// Skip the core schema
|
|
82
|
+
if (file === 'schema.sql') {
|
|
83
|
+
continue
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const match = file.match(schemaPattern)
|
|
87
|
+
if (match?.[1]) {
|
|
88
|
+
const layerName = match[1]
|
|
89
|
+
// Read SQL content at build time
|
|
90
|
+
try {
|
|
91
|
+
layerSchemas[layerName] = readFileSync(
|
|
92
|
+
join(publicDir, file),
|
|
93
|
+
'utf-8'
|
|
94
|
+
)
|
|
95
|
+
} catch {
|
|
96
|
+
console.error(
|
|
97
|
+
`[pluto-migrations] Failed to read schema file: ${file}`
|
|
98
|
+
)
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const layerNames = Object.keys(layerSchemas)
|
|
105
|
+
|
|
106
|
+
// Populate runtimeConfig with layer names and their SQL content.
|
|
107
|
+
// Nuxt's schema inference narrows this to a literal shape based on
|
|
108
|
+
// whatever layer keys were last observed in this project (see the
|
|
109
|
+
// `RuntimeConfig` augmentation in shared/types/runtime-config.d.ts,
|
|
110
|
+
// which isn't visible from this module-context tsconfig), so the
|
|
111
|
+
// assignment is cast to the intended general shape.
|
|
112
|
+
;(nuxt.options.runtimeConfig as { plutoLayerSchemas: Record<string, string> }).plutoLayerSchemas =
|
|
113
|
+
layerSchemas
|
|
114
|
+
|
|
115
|
+
if (layerNames.length > 0) {
|
|
116
|
+
console.warn(
|
|
117
|
+
`[pluto-migrations] Discovered layer schemas: ${layerNames.join(', ')}`
|
|
118
|
+
)
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
})
|
package/nuxt.config.ts
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
|
+
// Point these at a local checkout (e.g. `../pluto`, `../utils`) to test
|
|
2
|
+
// unpublished changes; unset, they resolve to the published npm packages.
|
|
3
|
+
const plutoLayer = process.env.PLUTO_PLUTO_PATH || '@plutocms/pluto'
|
|
4
|
+
const utilsLayer = process.env.PLUTO_UTILS_PATH || '@plutocms/utils'
|
|
5
|
+
|
|
1
6
|
export default defineNuxtConfig({
|
|
2
|
-
extends: [[
|
|
7
|
+
extends: [[plutoLayer, { install: true }], utilsLayer],
|
|
3
8
|
|
|
4
9
|
modules: ['@nuxt/eslint', '@nuxtjs/supabase'],
|
|
5
10
|
|
|
@@ -10,6 +15,8 @@ export default defineNuxtConfig({
|
|
|
10
15
|
runtimeConfig: {
|
|
11
16
|
supabaseUrl: process.env.SUPABASE_URL,
|
|
12
17
|
supabaseKey: process.env.SUPABASE_KEY,
|
|
18
|
+
plutoRootDir: '',
|
|
19
|
+
plutoLayerSchemas: {} as Record<string, string>,
|
|
13
20
|
},
|
|
14
21
|
|
|
15
22
|
alias: {
|
|
@@ -24,6 +31,12 @@ export default defineNuxtConfig({
|
|
|
24
31
|
},
|
|
25
32
|
},
|
|
26
33
|
|
|
34
|
+
vite: {
|
|
35
|
+
optimizeDeps: {
|
|
36
|
+
include: ['yup'],
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
|
|
27
40
|
eslint: {
|
|
28
41
|
config: {
|
|
29
42
|
nuxt: {
|
package/package.json
CHANGED
|
@@ -1,7 +1,26 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@plutocms/supabase",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"trustedDependencies": [
|
|
6
|
+
"@parcel/watcher",
|
|
7
|
+
"@plutocms/pluto",
|
|
8
|
+
"@plutocms/utils",
|
|
9
|
+
"esbuild",
|
|
10
|
+
"supabase",
|
|
11
|
+
"unrs-resolver",
|
|
12
|
+
"vue-demi"
|
|
13
|
+
],
|
|
14
|
+
"description": "Supabase backend layer for Pluto CMS",
|
|
15
|
+
"author": "Victor Ribeiro",
|
|
16
|
+
"homepage": "https://github.com/plutocms/supabase#readme",
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "git+https://github.com/plutocms/supabase.git"
|
|
20
|
+
},
|
|
21
|
+
"bugs": {
|
|
22
|
+
"url": "https://github.com/plutocms/supabase/issues"
|
|
23
|
+
},
|
|
5
24
|
"main": "./nuxt.config.ts",
|
|
6
25
|
"scripts": {
|
|
7
26
|
"build": "nuxt build",
|
|
@@ -12,35 +31,32 @@
|
|
|
12
31
|
"prettier": "prettier --write .",
|
|
13
32
|
"lint": "eslint .",
|
|
14
33
|
"lint:fix": "eslint . --fix && prettier --write .",
|
|
34
|
+
"typecheck": "nuxi typecheck",
|
|
15
35
|
"supabase-types": "bun ./scripts/supabase-typegen.ts"
|
|
16
36
|
},
|
|
17
37
|
"dependencies": {
|
|
18
|
-
"@
|
|
19
|
-
"@
|
|
20
|
-
"@plutocms/
|
|
21
|
-
"
|
|
22
|
-
"
|
|
38
|
+
"@nuxt/ui": "^4.9.0",
|
|
39
|
+
"@nuxtjs/supabase": "^2.0.9",
|
|
40
|
+
"@plutocms/pluto": "^0.1.0",
|
|
41
|
+
"@plutocms/utils": "^0.1.0",
|
|
42
|
+
"postgres": "^3.4.9",
|
|
43
|
+
"supabase": "^2.109.1",
|
|
23
44
|
"yup": "1.7.1"
|
|
24
45
|
},
|
|
25
46
|
"devDependencies": {
|
|
26
47
|
"@antfu/eslint-config": "^6.2.0",
|
|
27
|
-
"@nuxt/eslint": "^1.
|
|
28
|
-
"@
|
|
29
|
-
"@
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
"vue": "^3.5.
|
|
35
|
-
"vue-router": "^5.0
|
|
48
|
+
"@nuxt/eslint": "^1.16.0",
|
|
49
|
+
"@types/bun": "^1.3.14",
|
|
50
|
+
"@vueuse/nuxt": "^14.3.0",
|
|
51
|
+
"eslint": "^10.6.0",
|
|
52
|
+
"nuxt": "^4.4.2",
|
|
53
|
+
"prettier": "^3.9.4",
|
|
54
|
+
"tailwindcss": "^4.3.2",
|
|
55
|
+
"vue": "^3.5.39",
|
|
56
|
+
"vue-router": "^5.1.0",
|
|
57
|
+
"vue-tsc": "^3.3.7"
|
|
36
58
|
},
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
|
|
40
|
-
"@plutocms/utils",
|
|
41
|
-
"esbuild",
|
|
42
|
-
"supabase",
|
|
43
|
-
"unrs-resolver",
|
|
44
|
-
"vue-demi"
|
|
45
|
-
]
|
|
59
|
+
"publishConfig": {
|
|
60
|
+
"access": "public"
|
|
61
|
+
}
|
|
46
62
|
}
|