Compare commits
18 Commits
5a9aa57d83
...
pagina-log
| Author | SHA1 | Date | |
|---|---|---|---|
| 2b5f784d61 | |||
| f493a6f674 | |||
| 9b76618b71 | |||
| b365fca7eb | |||
| 35f32578e3 | |||
| b8dbb5869d | |||
| 404e2d457b | |||
| ed179f498a | |||
| cf80e63255 | |||
| b6f7d5e8a0 | |||
| 1b3910a001 | |||
| bb8a3c6f83 | |||
| 1acde7202e | |||
| 3e688c1d0f | |||
| f879e7b3f8 | |||
| 5dc80943fa | |||
| c323d4d6af | |||
| 6c4a7c5116 |
9
.prettierignore
Normal file
9
.prettierignore
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
node_modules
|
||||||
|
.nuxt
|
||||||
|
.output
|
||||||
|
.data
|
||||||
|
.cache
|
||||||
|
dist
|
||||||
|
coverage
|
||||||
|
.codex
|
||||||
|
package-lock.json
|
||||||
8
.prettierrc
Normal file
8
.prettierrc
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"plugins": ["prettier-plugin-tailwindcss"],
|
||||||
|
"printWidth": 100,
|
||||||
|
"semi": false,
|
||||||
|
"singleQuote": true,
|
||||||
|
"tabWidth": 2,
|
||||||
|
"trailingComma": "none"
|
||||||
|
}
|
||||||
3
.vscode/extensions.json
vendored
Normal file
3
.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"recommendations": ["esbenp.prettier-vscode", "Vue.volar"]
|
||||||
|
}
|
||||||
19
.vscode/settings.json
vendored
Normal file
19
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"editor.formatOnSave": true,
|
||||||
|
"[vue]": {
|
||||||
|
"editor.defaultFormatter": "Vue.volar"
|
||||||
|
},
|
||||||
|
"[typescript]": {
|
||||||
|
"editor.defaultFormatter": "vscode.typescript-language-features"
|
||||||
|
},
|
||||||
|
"[javascript]": {
|
||||||
|
"editor.defaultFormatter": "vscode.typescript-language-features"
|
||||||
|
},
|
||||||
|
"[json]": {
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
|
},
|
||||||
|
"[jsonc]": {
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
|
},
|
||||||
|
"prettier.requireConfig": true
|
||||||
|
}
|
||||||
20
README.md
20
README.md
@@ -39,7 +39,7 @@ npm run jwt:keys
|
|||||||
Distribua o valor de `JWT_PUBLIC_KEY_PEM` para todos os serviços consumidores.
|
Distribua o valor de `JWT_PUBLIC_KEY_PEM` para todos os serviços consumidores.
|
||||||
|
|
||||||
3. Crie `.env` a partir de `.env.example` e preencha os valores.
|
3. Crie `.env` a partir de `.env.example` e preencha os valores.
|
||||||
Se usar Postgres remoto com pool/proxy (ex.: Railway), use `DIRECT_DATABASE_URL` com conexão direta para migrations.
|
Se usar Postgres remoto com pool/proxy (ex.: Railway), use `DIRECT_DATABASE_URL` com conexão direta para migrations.
|
||||||
|
|
||||||
4. Execute migrações do Prisma:
|
4. Execute migrações do Prisma:
|
||||||
|
|
||||||
@@ -75,15 +75,15 @@ A tabela `User` possui apenas:
|
|||||||
|
|
||||||
## Endpoints
|
## Endpoints
|
||||||
|
|
||||||
| Método | Caminho | Auth | Descrição |
|
| Método | Caminho | Auth | Descrição |
|
||||||
|--------|---------|------|-----------|
|
| ------ | --------------------------- | ------------ | --------------------------- |
|
||||||
| POST | `/api/auth/register` | Não | Cria novo usuário |
|
| POST | `/api/auth/register` | Não | Cria novo usuário |
|
||||||
| POST | `/api/auth/login` | Não | Autentica e retorna tokens |
|
| POST | `/api/auth/login` | Não | Autentica e retorna tokens |
|
||||||
| POST | `/api/auth/refresh` | Não | Rotaciona refresh token |
|
| POST | `/api/auth/refresh` | Não | Rotaciona refresh token |
|
||||||
| POST | `/api/auth/forgot-password` | Não | Solicita reset de senha |
|
| POST | `/api/auth/forgot-password` | Não | Solicita reset de senha |
|
||||||
| POST | `/api/auth/reset-password` | Não | Aplica nova senha com token |
|
| POST | `/api/auth/reset-password` | Não | Aplica nova senha com token |
|
||||||
| GET | `/profile/me` | Sim (Bearer) | Retorna usuário autenticado |
|
| GET | `/profile/me` | Sim (Bearer) | Retorna usuário autenticado |
|
||||||
| GET | `/dashboard` | Sim (Bearer) | Orquestração A→B |
|
| GET | `/dashboard` | Sim (Bearer) | Orquestração A→B |
|
||||||
|
|
||||||
## Guia para serviços consumidores
|
## Guia para serviços consumidores
|
||||||
|
|
||||||
|
|||||||
@@ -2,4 +2,5 @@
|
|||||||
<NuxtLayout>
|
<NuxtLayout>
|
||||||
<NuxtPage />
|
<NuxtPage />
|
||||||
</NuxtLayout>
|
</NuxtLayout>
|
||||||
|
<Toaster position="top-right" rich-colors />
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
7
app/middleware/auth.ts
Normal file
7
app/middleware/auth.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
export default defineNuxtRouteMiddleware(() => {
|
||||||
|
const token = useCookie('token')
|
||||||
|
|
||||||
|
if (!token.value) {
|
||||||
|
return navigateTo('/login')
|
||||||
|
}
|
||||||
|
})
|
||||||
12
app/middleware/guest.ts
Normal file
12
app/middleware/guest.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
/**
|
||||||
|
* Middleware para rotas guest.
|
||||||
|
* Redireciona para a página de home se o usuário estiver autenticado.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export default defineNuxtRouteMiddleware(() => {
|
||||||
|
const token = useCookie('token')
|
||||||
|
|
||||||
|
if (token.value) {
|
||||||
|
return navigateTo('/home')
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -1,35 +1,120 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div
|
||||||
<h1>Oooiiii</h1>
|
class="relative grid min-h-screen place-items-center overflow-hidden bg-[#f5f5f5] px-4 py-12 font-sans text-[#0c0a09] md:py-24">
|
||||||
<label for="email">Email: </label>
|
<div
|
||||||
<input v-model="formData.email" type="text" id="email" name="email" />
|
class="pointer-events-none absolute left-1/2 top-10 h-[420px] w-[min(92vw,760px)] -translate-x-1/2 rounded-[48px] bg-[radial-gradient(circle_at_20%_30%,rgba(167,229,211,0.62),transparent_28%),radial-gradient(circle_at_72%_24%,rgba(244,197,168,0.56),transparent_30%),radial-gradient(circle_at_52%_78%,rgba(200,184,224,0.5),transparent_34%)] blur-2xl"
|
||||||
<label for="senha">Senha: </label>
|
aria-hidden="true"></div>
|
||||||
<input v-model="formData.password" type="password" id="senha" name="senha" />
|
|
||||||
<button @click="criarConta">Criar Conta</button>
|
<main
|
||||||
</div>
|
class="relative grid w-full max-w-[1120px] grid-cols-1 items-center gap-8 md:grid-cols-[minmax(0,1fr)_minmax(340px,420px)] md:gap-16"
|
||||||
|
aria-labelledby="create-account-title">
|
||||||
|
<section class="relative pt-2 md:py-8">
|
||||||
|
<p
|
||||||
|
class="m-0 inline-flex rounded-full bg-[rgba(244,197,168,0.56)] px-3 py-1 text-xs font-semibold uppercase leading-[1.4] tracking-[0.96px] text-[#0c0a09]">
|
||||||
|
Nova conta
|
||||||
|
</p>
|
||||||
|
<h1 id="create-account-title"
|
||||||
|
class="my-5 max-w-[640px] font-serif text-[40px] font-light leading-[1.05] tracking-[-1.2px] text-[#0c0a09] md:text-[64px] md:tracking-[-1.92px]">
|
||||||
|
Crie sua conta
|
||||||
|
</h1>
|
||||||
|
<p class="m-0 max-w-[520px] text-base font-normal leading-6 tracking-[0.16px] text-[#4e4e4e]">
|
||||||
|
Crie sua conta de forma simples e rápida com email e senha.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section
|
||||||
|
class="grid gap-5 rounded-2xl border border-[#e7e5e4] bg-white p-6 shadow-[0_4px_16px_rgba(0,0,0,0.04)] md:p-8"
|
||||||
|
aria-label="Formulario de criacao de conta">
|
||||||
|
<div class="mb-1">
|
||||||
|
<span
|
||||||
|
class="inline-flex min-h-6 items-center rounded-full bg-[rgba(244,197,168,0.56)] px-2.5 py-1 text-xs font-semibold uppercase leading-none tracking-[0.96px] text-[#0c0a09]">
|
||||||
|
Cadastro
|
||||||
|
</span>
|
||||||
|
<h2 class="mt-4 font-serif text-3xl font-light leading-[1.13] tracking-[-0.32px] text-[#0c0a09]">
|
||||||
|
Dados de entrada
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Form :validation-schema="schema" @submit="criarConta" class="grid gap-5">
|
||||||
|
<div class="grid gap-2">
|
||||||
|
<label class="m-0 text-xs font-semibold uppercase leading-[1.4] tracking-[0.96px] text-[#777169]"
|
||||||
|
for="email">
|
||||||
|
Email
|
||||||
|
</label>
|
||||||
|
<Field name="email" v-slot="{ field, meta }">
|
||||||
|
<input v-bind="field" :disabled="isLoading"
|
||||||
|
class="h-11 w-full rounded-lg border bg-white px-4 py-3 text-base leading-6 tracking-[0.16px] text-[#0c0a09] outline-none transition focus:border-2 focus:border-[#0c0a09] disabled:cursor-not-allowed disabled:opacity-50"
|
||||||
|
:class="meta.touched && !meta.valid ? 'border-red-400' : 'border-[#d6d3d1]'" type="email" id="email" />
|
||||||
|
</Field>
|
||||||
|
<ErrorMessage name="email" class="text-xs text-red-500" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid gap-2">
|
||||||
|
<label class="m-0 text-xs font-semibold uppercase leading-[1.4] tracking-[0.96px] text-[#777169]"
|
||||||
|
for="senha">
|
||||||
|
Senha
|
||||||
|
</label>
|
||||||
|
<Field name="password" v-slot="{ field, meta }">
|
||||||
|
<input v-bind="field" :disabled="isLoading"
|
||||||
|
class="h-11 w-full rounded-lg border bg-white px-4 py-3 text-base leading-6 tracking-[0.16px] text-[#0c0a09] outline-none transition focus:border-2 focus:border-[#0c0a09] disabled:cursor-not-allowed disabled:opacity-50"
|
||||||
|
:class="meta.touched && !meta.valid ? 'border-red-400' : 'border-[#d6d3d1]'" type="password"
|
||||||
|
id="senha" />
|
||||||
|
</Field>
|
||||||
|
<ErrorMessage name="password" class="text-xs text-red-500" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit" :disabled="isLoading"
|
||||||
|
class="mt-1 inline-flex h-10 items-center justify-center gap-2 rounded-full bg-[#292524] px-5 text-[15px] font-medium leading-none text-white transition hover:bg-[#0c0a09] active:bg-[#0c0a09] disabled:cursor-not-allowed disabled:opacity-70">
|
||||||
|
<Icon v-if="isLoading" name="mdi:loading" class="animate-spin text-base" />
|
||||||
|
<span v-else>Criar Conta</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<p class="text-center text-[15px] leading-[1.47] tracking-[0.15px] text-[#777169]">
|
||||||
|
Já tem uma conta?
|
||||||
|
<NuxtLink to="/login" class="font-medium text-[#0c0a09] underline-offset-2 hover:underline">
|
||||||
|
Fazer login
|
||||||
|
</NuxtLink>
|
||||||
|
</p>
|
||||||
|
</Form>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
import { toTypedSchema } from '@vee-validate/zod'
|
||||||
|
import { z } from 'zod'
|
||||||
|
|
||||||
const formData = reactive({
|
definePageMeta({ middleware: 'guest' })
|
||||||
email: '',
|
|
||||||
password: ''
|
|
||||||
})
|
|
||||||
|
|
||||||
async function criarConta() {
|
const { $toast } = useNuxtApp()
|
||||||
try {
|
|
||||||
(await fetch('/auth/register', {
|
const isLoading = ref(false)
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
const toStr = (val) => (val == null ? '' : String(val))
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
const schema = toTypedSchema(
|
||||||
body: JSON.stringify({
|
z.object({
|
||||||
email: formData.email,
|
email: z.preprocess(toStr, z.string().min(1, 'Email é obrigatório').email('Email inválido')),
|
||||||
password: formData.password
|
password: z.preprocess(toStr, z.string().min(8, 'A senha deve ter no mínimo 8 caracteres')),
|
||||||
})
|
}),
|
||||||
}))()
|
)
|
||||||
} catch (error) {
|
|
||||||
window.alert('Ocorreu um erro ao criar a conta. Tente novamente mais tarde.', error.statusText)
|
async function criarConta({ email, password }) {
|
||||||
}
|
isLoading.value = true
|
||||||
|
try {
|
||||||
|
await $fetch('/auth/register', {
|
||||||
|
method: 'POST',
|
||||||
|
body: { email, password },
|
||||||
|
})
|
||||||
|
|
||||||
|
$toast.success('Conta criada com sucesso!', { duration: 8000 })
|
||||||
|
await navigateTo('/login')
|
||||||
|
} catch (error) {
|
||||||
|
const message = error?.data?.statusMessage
|
||||||
|
$toast.error(message ?? 'Erro ao criar conta. Tente novamente.', { duration: 8000 })
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
133
app/pages/(auth)/login/index.vue
Normal file
133
app/pages/(auth)/login/index.vue
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="relative grid min-h-screen place-items-center overflow-hidden bg-[#f5f5f5] px-4 py-12 font-sans text-[#0c0a09] md:py-24">
|
||||||
|
<div class="pointer-events-none absolute left-1/2 top-10 h-[420px] w-[min(92vw,760px)] -translate-x-1/2 rounded-[48px] bg-[radial-gradient(circle_at_20%_30%,rgba(167,229,211,0.62),transparent_28%),radial-gradient(circle_at_72%_24%,rgba(244,197,168,0.56),transparent_30%),radial-gradient(circle_at_52%_78%,rgba(200,184,224,0.5),transparent_34%)] blur-2xl"
|
||||||
|
aria-hidden="true"></div>
|
||||||
|
<main
|
||||||
|
class="relative grid w-full max-w-[1120px] grid-cols-1 items-center gap-8 md:grid-cols-[minmax(0,1fr)_minmax(340px,420px)] md:gap-16"
|
||||||
|
aria-labelledby="create-account-title">
|
||||||
|
<section class="relative pt-2 md:py-8">
|
||||||
|
<p
|
||||||
|
class="m-0 inline-flex rounded-full bg-[rgba(200,184,224,0.5)] px-3 py-1 text-xs font-semibold uppercase leading-[1.4] tracking-[0.96px] text-[#0c0a09]">
|
||||||
|
Login
|
||||||
|
</p>
|
||||||
|
<h1 id="create-account-title"
|
||||||
|
class="my-5 max-w-[640px] font-serif text-[40px] font-light leading-[1.05] tracking-[-1.2px] text-[#0c0a09] md:text-[64px] md:tracking-[-1.92px]">
|
||||||
|
Faça seu login
|
||||||
|
</h1>
|
||||||
|
<p class="m-0 max-w-[520px] text-base font-normal leading-6 tracking-[0.16px] text-[#4e4e4e]">
|
||||||
|
Faça seu login com email e senha.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section
|
||||||
|
class="grid gap-5 rounded-2xl border border-[#e7e5e4] bg-white p-6 shadow-[0_4px_16px_rgba(0,0,0,0.04)] md:p-8"
|
||||||
|
aria-label="Formulario de criacao de conta">
|
||||||
|
<div class="mb-1">
|
||||||
|
<span
|
||||||
|
class="inline-flex min-h-6 items-center rounded-full bg-[rgba(200,184,224,0.5)] px-2.5 py-1 text-xs font-semibold uppercase leading-none tracking-[0.96px] text-[#0c0a09]">
|
||||||
|
Login
|
||||||
|
</span>
|
||||||
|
<h2 class="mt-4 font-serif text-3xl font-light leading-[1.13] tracking-[-0.32px] text-[#0c0a09]">
|
||||||
|
Dados de entrada
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Form :validation-schema="schema" @submit="entrarConta" class="grid gap-5">
|
||||||
|
<div class="grid gap-2">
|
||||||
|
<label
|
||||||
|
class="m-0 text-xs font-semibold uppercase leading-[1.4] tracking-[0.96px] text-[#777169]"
|
||||||
|
for="email">
|
||||||
|
Email
|
||||||
|
</label>
|
||||||
|
<Field name="email" v-slot="{ field, meta }">
|
||||||
|
<input v-bind="field" :disabled="isLoading"
|
||||||
|
class="h-11 w-full rounded-lg border bg-white px-4 py-3 text-base leading-6 tracking-[0.16px] text-[#0c0a09] outline-none transition focus:border-2 focus:border-[#0c0a09] disabled:cursor-not-allowed disabled:opacity-50"
|
||||||
|
:class="meta.touched && !meta.valid ? 'border-red-400' : 'border-[#d6d3d1]'"
|
||||||
|
type="email" id="email" />
|
||||||
|
</Field>
|
||||||
|
<ErrorMessage name="email" class="text-xs text-red-500" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid gap-2">
|
||||||
|
<label
|
||||||
|
class="m-0 text-xs font-semibold uppercase leading-[1.4] tracking-[0.96px] text-[#777169]"
|
||||||
|
for="senha">
|
||||||
|
Senha
|
||||||
|
</label>
|
||||||
|
<Field name="password" v-slot="{ field, meta }">
|
||||||
|
<input v-bind="field" :disabled="isLoading"
|
||||||
|
class="h-11 w-full rounded-lg border bg-white px-4 py-3 text-base leading-6 tracking-[0.16px] text-[#0c0a09] outline-none transition focus:border-2 focus:border-[#0c0a09] disabled:cursor-not-allowed disabled:opacity-50"
|
||||||
|
:class="meta.touched && !meta.valid ? 'border-red-400' : 'border-[#d6d3d1]'"
|
||||||
|
type="password" id="senha" />
|
||||||
|
</Field>
|
||||||
|
<ErrorMessage name="password" class="text-xs text-red-500" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit" :disabled="isLoading"
|
||||||
|
class="mt-1 inline-flex h-10 items-center justify-center gap-2 rounded-full bg-[#292524] px-5 text-[15px] font-medium leading-none text-white transition hover:bg-[#0c0a09] active:bg-[#0c0a09] disabled:cursor-not-allowed disabled:opacity-70">
|
||||||
|
<Icon v-if="isLoading" name="mdi:loading" class="animate-spin text-base" />
|
||||||
|
<span v-else>Entrar</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<p class="text-center text-[15px] leading-[1.47] tracking-[0.15px] text-[#777169]">
|
||||||
|
Não tem uma conta?
|
||||||
|
<NuxtLink to="/criar-conta"
|
||||||
|
class="font-medium text-[#0c0a09] underline-offset-2 hover:underline">
|
||||||
|
Criar conta
|
||||||
|
</NuxtLink>
|
||||||
|
</p>
|
||||||
|
</Form>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { toTypedSchema } from '@vee-validate/zod'
|
||||||
|
import { z } from 'zod'
|
||||||
|
|
||||||
|
definePageMeta({ middleware: 'guest' })
|
||||||
|
|
||||||
|
const { $toast } = useNuxtApp()
|
||||||
|
|
||||||
|
const token = useCookie('token', {
|
||||||
|
secure: true,
|
||||||
|
sameSite: 'lax',
|
||||||
|
maxAge: 900
|
||||||
|
})
|
||||||
|
|
||||||
|
const isLoading = ref(false)
|
||||||
|
|
||||||
|
const toStr = (val) => (val == null ? '' : String(val))
|
||||||
|
|
||||||
|
const schema = toTypedSchema(
|
||||||
|
z.object({
|
||||||
|
email: z.preprocess(toStr, z.string().min(1, 'Email é obrigatório').email('Email inválido')),
|
||||||
|
password: z.preprocess(toStr, z.string().min(1, 'A senha é obrigatória')),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
async function entrarConta({ email, password }) {
|
||||||
|
isLoading.value = true
|
||||||
|
try {
|
||||||
|
const data = await $fetch('/auth/login', {
|
||||||
|
method: 'POST',
|
||||||
|
body: { email, password },
|
||||||
|
})
|
||||||
|
|
||||||
|
token.value = data.access_token
|
||||||
|
|
||||||
|
$toast.success('Login realizado com sucesso!', { duration: 8000 })
|
||||||
|
|
||||||
|
await navigateTo('/home')
|
||||||
|
} catch (error) {
|
||||||
|
const message = error?.data?.statusMessage
|
||||||
|
$toast.error(message ?? 'Erro para efetuar o login. Tente novamente.', {
|
||||||
|
duration: 8000,
|
||||||
|
})
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
11
app/pages/(protected)/home/index.vue
Normal file
11
app/pages/(protected)/home/index.vue
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<h1>OOOIII SOU A HOMEEE</h1>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
definePageMeta({
|
||||||
|
middleware: 'auth'
|
||||||
|
})
|
||||||
|
</script>
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<h1>Welcome to the Home Page</h1>
|
<h1>Welcome to the Home Page</h1>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ const prismaClientSingleton = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
declare const globalThis: {
|
declare const globalThis: {
|
||||||
prismaGlobal: ReturnType<typeof prismaClientSingleton>;
|
prismaGlobal: ReturnType<typeof prismaClientSingleton>
|
||||||
} & typeof global;
|
} & typeof global
|
||||||
|
|
||||||
const prisma = globalThis.prismaGlobal ?? prismaClientSingleton()
|
const prisma = globalThis.prismaGlobal ?? prismaClientSingleton()
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
export default defineNuxtConfig({
|
export default defineNuxtConfig({
|
||||||
compatibilityDate: '2025-07-15',
|
compatibilityDate: '2025-07-15',
|
||||||
devtools: { enabled: true },
|
devtools: { enabled: true },
|
||||||
modules: ['@nuxtjs/tailwindcss', '@pinia/nuxt', '@prisma/nuxt'],
|
modules: ['@nuxtjs/tailwindcss', '@pinia/nuxt', '@prisma/nuxt', '@vee-validate/nuxt', 'vue-sonner/nuxt', '@nuxt/icon'],
|
||||||
runtimeConfig: {
|
runtimeConfig: {
|
||||||
databaseUrl: process.env.DATABASE_URL,
|
databaseUrl: process.env.DATABASE_URL,
|
||||||
jwtIssuer: process.env.JWT_ISSUER ?? 'https://auth.local',
|
jwtIssuer: process.env.JWT_ISSUER ?? 'https://auth.local',
|
||||||
@@ -18,7 +18,14 @@ export default defineNuxtConfig({
|
|||||||
},
|
},
|
||||||
vite: {
|
vite: {
|
||||||
optimizeDeps: {
|
optimizeDeps: {
|
||||||
include: ['@vue/devtools-core', '@vue/devtools-kit']
|
include: [
|
||||||
|
'@vue/devtools-core',
|
||||||
|
'@vue/devtools-kit',
|
||||||
|
'@prisma/nuxt > @prisma/client',
|
||||||
|
'zod',
|
||||||
|
'@vee-validate/zod',
|
||||||
|
'vue-sonner',
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
450
package-lock.json
generated
450
package-lock.json
generated
@@ -7,18 +7,26 @@
|
|||||||
"name": "sistema",
|
"name": "sistema",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@nuxt/icon": "^2.2.1",
|
||||||
"@nuxtjs/tailwindcss": "^6.14.0",
|
"@nuxtjs/tailwindcss": "^6.14.0",
|
||||||
"@pinia/nuxt": "^0.11.3",
|
"@pinia/nuxt": "^0.11.3",
|
||||||
"@prisma/client": "^6.16.2",
|
"@prisma/client": "^6.16.2",
|
||||||
"@prisma/nuxt": "^0.3.0",
|
"@prisma/nuxt": "^0.3.0",
|
||||||
|
"@vee-validate/nuxt": "^4.15.1",
|
||||||
|
"@vee-validate/zod": "^4.15.1",
|
||||||
"jose": "^6.2.2",
|
"jose": "^6.2.2",
|
||||||
"nuxt": "^4.4.2",
|
"nuxt": "^4.4.2",
|
||||||
|
"pinia": "^3.0.4",
|
||||||
"prisma": "^6.16.2",
|
"prisma": "^6.16.2",
|
||||||
"vue": "^3.5.32",
|
"vue": "^3.5.32",
|
||||||
"vue-router": "^5.0.4"
|
"vue-router": "^5.0.4",
|
||||||
|
"vue-sonner": "^2.0.9",
|
||||||
|
"zod": "^3.25.76"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^25.6.0"
|
"@types/node": "^25.6.0",
|
||||||
|
"prettier": "^3.8.3",
|
||||||
|
"prettier-plugin-tailwindcss": "^0.8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@alloc/quick-lru": {
|
"node_modules/@alloc/quick-lru": {
|
||||||
@@ -33,6 +41,19 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@antfu/install-pkg": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"package-manager-detector": "^1.3.0",
|
||||||
|
"tinyexec": "^1.0.1"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/antfu"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@babel/code-frame": {
|
"node_modules/@babel/code-frame": {
|
||||||
"version": "7.29.0",
|
"version": "7.29.0",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz",
|
||||||
@@ -1008,6 +1029,47 @@
|
|||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@iconify/collections": {
|
||||||
|
"version": "1.0.677",
|
||||||
|
"resolved": "https://registry.npmjs.org/@iconify/collections/-/collections-1.0.677.tgz",
|
||||||
|
"integrity": "sha512-Uwbl1p26Md6m2xTMjalJsmQ43YxW+j6RsyrL5RnxRCHEhwympJlaBBCGTKrqI3sA93g9UVJ/Pxi7zDqM3E1oPw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@iconify/types": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@iconify/types": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@iconify/utils": {
|
||||||
|
"version": "3.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@iconify/utils/-/utils-3.1.1.tgz",
|
||||||
|
"integrity": "sha512-MwzoDtw9rO1x+qfgLTV/IVXsHDBqeYZoMIQC8SfxfYSlaSUG+oWiAcoiB1yajAda6mqblm4/1/w2E8tRu7a7Tw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@antfu/install-pkg": "^1.1.0",
|
||||||
|
"@iconify/types": "^2.0.0",
|
||||||
|
"mlly": "^1.8.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@iconify/vue": {
|
||||||
|
"version": "5.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@iconify/vue/-/vue-5.0.0.tgz",
|
||||||
|
"integrity": "sha512-C+KuEWIF5nSBrobFJhT//JS87OZ++QDORB6f2q2Wm6fl2mueSTpFBeBsveK0KW9hWiZ4mNiPjsh6Zs4jjdROSg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@iconify/types": "^2.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/cyberalien"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"vue": ">=3"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@ioredis/commands": {
|
"node_modules/@ioredis/commands": {
|
||||||
"version": "1.5.1",
|
"version": "1.5.1",
|
||||||
"resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.5.1.tgz",
|
||||||
@@ -1393,6 +1455,34 @@
|
|||||||
"devtools-wizard": "cli.mjs"
|
"devtools-wizard": "cli.mjs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@nuxt/icon": {
|
||||||
|
"version": "2.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@nuxt/icon/-/icon-2.2.1.tgz",
|
||||||
|
"integrity": "sha512-GI840yYGuvHI0BGDQ63d6rAxGzG96jQcWrnaWIQKlyQo/7sx9PjXkSHckXUXyX1MCr9zY6U25Td6OatfY6Hklw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@iconify/collections": "^1.0.641",
|
||||||
|
"@iconify/types": "^2.0.0",
|
||||||
|
"@iconify/utils": "^3.1.0",
|
||||||
|
"@iconify/vue": "^5.0.0",
|
||||||
|
"@nuxt/devtools-kit": "^3.1.1",
|
||||||
|
"@nuxt/kit": "^4.2.2",
|
||||||
|
"consola": "^3.4.2",
|
||||||
|
"local-pkg": "^1.1.2",
|
||||||
|
"mlly": "^1.8.0",
|
||||||
|
"ohash": "^2.0.11",
|
||||||
|
"pathe": "^2.0.3",
|
||||||
|
"picomatch": "^4.0.3",
|
||||||
|
"std-env": "^3.10.0",
|
||||||
|
"tinyglobby": "^0.2.15"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@nuxt/icon/node_modules/std-env": {
|
||||||
|
"version": "3.10.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz",
|
||||||
|
"integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@nuxt/kit": {
|
"node_modules/@nuxt/kit": {
|
||||||
"version": "4.4.2",
|
"version": "4.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/@nuxt/kit/-/kit-4.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/@nuxt/kit/-/kit-4.4.2.tgz",
|
||||||
@@ -3944,6 +4034,118 @@
|
|||||||
"vue": ">=3.5.18"
|
"vue": ">=3.5.18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@vee-validate/nuxt": {
|
||||||
|
"version": "4.15.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vee-validate/nuxt/-/nuxt-4.15.1.tgz",
|
||||||
|
"integrity": "sha512-vy+BYUmwy7d01js8jg3qApyrE8/nJHbHvPZNYZSe+hTQEblNqyVt+MnD1incyxMCcivED8FX5w4rjxdoUn7iMQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@nuxt/kit": "^3.13.2",
|
||||||
|
"local-pkg": "^0.5.0",
|
||||||
|
"vee-validate": "4.15.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@vee-validate/nuxt/node_modules/@nuxt/kit": {
|
||||||
|
"version": "3.21.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@nuxt/kit/-/kit-3.21.4.tgz",
|
||||||
|
"integrity": "sha512-XDWhQJsA5hpdFpVSmImQIVXcsANJI07TjT1LZC/AUKJxl/dcM52Rq4uU+b3uqyVl4LZR1fODSDEzLxcdXq4Rmg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"c12": "^3.3.4",
|
||||||
|
"consola": "^3.4.2",
|
||||||
|
"defu": "^6.1.7",
|
||||||
|
"destr": "^2.0.5",
|
||||||
|
"errx": "^0.1.0",
|
||||||
|
"exsolve": "^1.0.8",
|
||||||
|
"ignore": "^7.0.5",
|
||||||
|
"jiti": "^2.6.1",
|
||||||
|
"klona": "^2.0.6",
|
||||||
|
"knitwork": "^1.3.0",
|
||||||
|
"mlly": "^1.8.2",
|
||||||
|
"ohash": "^2.0.11",
|
||||||
|
"pathe": "^2.0.3",
|
||||||
|
"pkg-types": "^2.3.1",
|
||||||
|
"rc9": "^3.0.1",
|
||||||
|
"scule": "^1.3.0",
|
||||||
|
"semver": "^7.7.4",
|
||||||
|
"tinyglobby": "^0.2.16",
|
||||||
|
"ufo": "^1.6.4",
|
||||||
|
"unctx": "^2.5.0",
|
||||||
|
"untyped": "^2.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.12.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@vee-validate/nuxt/node_modules/local-pkg": {
|
||||||
|
"version": "0.5.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.1.tgz",
|
||||||
|
"integrity": "sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"mlly": "^1.7.3",
|
||||||
|
"pkg-types": "^1.2.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/antfu"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@vee-validate/nuxt/node_modules/local-pkg/node_modules/confbox": {
|
||||||
|
"version": "0.1.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz",
|
||||||
|
"integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@vee-validate/nuxt/node_modules/local-pkg/node_modules/pkg-types": {
|
||||||
|
"version": "1.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz",
|
||||||
|
"integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"confbox": "^0.1.8",
|
||||||
|
"mlly": "^1.7.4",
|
||||||
|
"pathe": "^2.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@vee-validate/nuxt/node_modules/pkg-types": {
|
||||||
|
"version": "2.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.1.tgz",
|
||||||
|
"integrity": "sha512-y+ichcgc2LrADuhLNAx8DFjVfgz91pRxfZdI3UDhxHvcVEZsenLO+7XaU5vOp0u/7V/wZ+plyuQxtrDlZJ+yeg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"confbox": "^0.2.4",
|
||||||
|
"exsolve": "^1.0.8",
|
||||||
|
"pathe": "^2.0.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@vee-validate/zod": {
|
||||||
|
"version": "4.15.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vee-validate/zod/-/zod-4.15.1.tgz",
|
||||||
|
"integrity": "sha512-329Z4TDBE5Vx0FdbA8S4eR9iGCFFUNGbxjpQ20ff5b5wGueScjocUIx9JHPa79LTG06RnlUR4XogQsjN4tecKA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"type-fest": "^4.8.3",
|
||||||
|
"vee-validate": "4.15.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"zod": "^3.24.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@vee-validate/zod/node_modules/type-fest": {
|
||||||
|
"version": "4.41.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz",
|
||||||
|
"integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==",
|
||||||
|
"license": "(MIT OR CC0-1.0)",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@vercel/nft": {
|
"node_modules/@vercel/nft": {
|
||||||
"version": "1.5.0",
|
"version": "1.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/@vercel/nft/-/nft-1.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/@vercel/nft/-/nft-1.5.0.tgz",
|
||||||
@@ -5273,7 +5475,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-4.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-4.0.5.tgz",
|
||||||
"integrity": "sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==",
|
"integrity": "sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"is-what": "^5.2.0"
|
"is-what": "^5.2.0"
|
||||||
},
|
},
|
||||||
@@ -7045,7 +7246,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/is-what/-/is-what-5.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/is-what/-/is-what-5.5.0.tgz",
|
||||||
"integrity": "sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw==",
|
"integrity": "sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
},
|
},
|
||||||
@@ -7764,8 +7964,7 @@
|
|||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz",
|
||||||
"integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==",
|
"integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==",
|
||||||
"license": "MIT",
|
"license": "MIT"
|
||||||
"peer": true
|
|
||||||
},
|
},
|
||||||
"node_modules/mlly": {
|
"node_modules/mlly": {
|
||||||
"version": "1.8.2",
|
"version": "1.8.2",
|
||||||
@@ -8434,6 +8633,12 @@
|
|||||||
"integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
|
"integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
|
||||||
"license": "BlueOak-1.0.0"
|
"license": "BlueOak-1.0.0"
|
||||||
},
|
},
|
||||||
|
"node_modules/package-manager-detector": {
|
||||||
|
"version": "1.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.6.0.tgz",
|
||||||
|
"integrity": "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/parseurl": {
|
"node_modules/parseurl": {
|
||||||
"version": "1.3.3",
|
"version": "1.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
|
||||||
@@ -8542,7 +8747,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/pinia/-/pinia-3.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/pinia/-/pinia-3.0.4.tgz",
|
||||||
"integrity": "sha512-l7pqLUFTI/+ESXn6k3nu30ZIzW5E2WZF/LaHJEpoq6ElcLD+wduZoB2kBN19du6K/4FDpPMazY2wJr+IndBtQw==",
|
"integrity": "sha512-l7pqLUFTI/+ESXn6k3nu30ZIzW5E2WZF/LaHJEpoq6ElcLD+wduZoB2kBN19du6K/4FDpPMazY2wJr+IndBtQw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vue/devtools-api": "^7.7.7"
|
"@vue/devtools-api": "^7.7.7"
|
||||||
},
|
},
|
||||||
@@ -8564,7 +8768,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-7.7.9.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-7.7.9.tgz",
|
||||||
"integrity": "sha512-kIE8wvwlcZ6TJTbNeU2HQNtaxLx3a84aotTITUuL/4bzfPxzajGBOoqjMhwZJ8L9qFYDU/lAYMEEm11dnZOD6g==",
|
"integrity": "sha512-kIE8wvwlcZ6TJTbNeU2HQNtaxLx3a84aotTITUuL/4bzfPxzajGBOoqjMhwZJ8L9qFYDU/lAYMEEm11dnZOD6g==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vue/devtools-kit": "^7.7.9"
|
"@vue/devtools-kit": "^7.7.9"
|
||||||
}
|
}
|
||||||
@@ -8574,7 +8777,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.7.9.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.7.9.tgz",
|
||||||
"integrity": "sha512-PyQ6odHSgiDVd4hnTP+aDk2X4gl2HmLDfiyEnn3/oV+ckFDuswRs4IbBT7vacMuGdwY/XemxBoh302ctbsptuA==",
|
"integrity": "sha512-PyQ6odHSgiDVd4hnTP+aDk2X4gl2HmLDfiyEnn3/oV+ckFDuswRs4IbBT7vacMuGdwY/XemxBoh302ctbsptuA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vue/devtools-shared": "^7.7.9",
|
"@vue/devtools-shared": "^7.7.9",
|
||||||
"birpc": "^2.3.0",
|
"birpc": "^2.3.0",
|
||||||
@@ -8590,7 +8792,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.7.9.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.7.9.tgz",
|
||||||
"integrity": "sha512-iWAb0v2WYf0QWmxCGy0seZNDPdO3Sp5+u78ORnyeonS6MT4PC7VPrryX2BpMJrwlDeaZ6BD4vP4XKjK0SZqaeA==",
|
"integrity": "sha512-iWAb0v2WYf0QWmxCGy0seZNDPdO3Sp5+u78ORnyeonS6MT4PC7VPrryX2BpMJrwlDeaZ6BD4vP4XKjK0SZqaeA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"rfdc": "^1.4.1"
|
"rfdc": "^1.4.1"
|
||||||
}
|
}
|
||||||
@@ -8600,7 +8801,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/birpc/-/birpc-2.9.0.tgz",
|
"resolved": "https://registry.npmjs.org/birpc/-/birpc-2.9.0.tgz",
|
||||||
"integrity": "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==",
|
"integrity": "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/antfu"
|
"url": "https://github.com/sponsors/antfu"
|
||||||
}
|
}
|
||||||
@@ -8609,15 +8809,13 @@
|
|||||||
"version": "5.5.3",
|
"version": "5.5.3",
|
||||||
"resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz",
|
"resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz",
|
||||||
"integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==",
|
"integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==",
|
||||||
"license": "MIT",
|
"license": "MIT"
|
||||||
"peer": true
|
|
||||||
},
|
},
|
||||||
"node_modules/pinia/node_modules/perfect-debounce": {
|
"node_modules/pinia/node_modules/perfect-debounce": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz",
|
||||||
"integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==",
|
"integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==",
|
||||||
"license": "MIT",
|
"license": "MIT"
|
||||||
"peer": true
|
|
||||||
},
|
},
|
||||||
"node_modules/pirates": {
|
"node_modules/pirates": {
|
||||||
"version": "4.0.7",
|
"version": "4.0.7",
|
||||||
@@ -9271,6 +9469,101 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/prettier": {
|
||||||
|
"version": "3.8.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.3.tgz",
|
||||||
|
"integrity": "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"prettier": "bin/prettier.cjs"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/prettier/prettier?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/prettier-plugin-tailwindcss": {
|
||||||
|
"version": "0.8.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.8.0.tgz",
|
||||||
|
"integrity": "sha512-V8ITGH87yuBDF6JpEZTOVlUz/saAwqb8f3HRgUj8Lh+tGCcrmorhsLpYqzygwFwK0PE2Ib6Mv3M7T/uE2tZV1g==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20.19"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@ianvs/prettier-plugin-sort-imports": "*",
|
||||||
|
"@prettier/plugin-hermes": "*",
|
||||||
|
"@prettier/plugin-oxc": "*",
|
||||||
|
"@prettier/plugin-pug": "*",
|
||||||
|
"@shopify/prettier-plugin-liquid": "*",
|
||||||
|
"@trivago/prettier-plugin-sort-imports": "*",
|
||||||
|
"@zackad/prettier-plugin-twig": "*",
|
||||||
|
"prettier": "^3.0",
|
||||||
|
"prettier-plugin-astro": "*",
|
||||||
|
"prettier-plugin-css-order": "*",
|
||||||
|
"prettier-plugin-jsdoc": "*",
|
||||||
|
"prettier-plugin-marko": "*",
|
||||||
|
"prettier-plugin-multiline-arrays": "*",
|
||||||
|
"prettier-plugin-organize-attributes": "*",
|
||||||
|
"prettier-plugin-organize-imports": "*",
|
||||||
|
"prettier-plugin-sort-imports": "*",
|
||||||
|
"prettier-plugin-svelte": "*"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@ianvs/prettier-plugin-sort-imports": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@prettier/plugin-hermes": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@prettier/plugin-oxc": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@prettier/plugin-pug": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@shopify/prettier-plugin-liquid": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@trivago/prettier-plugin-sort-imports": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@zackad/prettier-plugin-twig": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"prettier-plugin-astro": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"prettier-plugin-css-order": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"prettier-plugin-jsdoc": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"prettier-plugin-marko": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"prettier-plugin-multiline-arrays": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"prettier-plugin-organize-attributes": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"prettier-plugin-organize-imports": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"prettier-plugin-sort-imports": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"prettier-plugin-svelte": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/pretty-bytes": {
|
"node_modules/pretty-bytes": {
|
||||||
"version": "7.1.0",
|
"version": "7.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-7.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-7.1.0.tgz",
|
||||||
@@ -9795,8 +10088,7 @@
|
|||||||
"version": "1.4.1",
|
"version": "1.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz",
|
||||||
"integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==",
|
"integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==",
|
||||||
"license": "MIT",
|
"license": "MIT"
|
||||||
"peer": true
|
|
||||||
},
|
},
|
||||||
"node_modules/rollup": {
|
"node_modules/rollup": {
|
||||||
"version": "4.60.1",
|
"version": "4.60.1",
|
||||||
@@ -10200,7 +10492,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/speakingurl/-/speakingurl-14.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/speakingurl/-/speakingurl-14.0.1.tgz",
|
||||||
"integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==",
|
"integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==",
|
||||||
"license": "BSD-3-Clause",
|
"license": "BSD-3-Clause",
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
@@ -10442,7 +10733,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/superjson/-/superjson-2.2.6.tgz",
|
"resolved": "https://registry.npmjs.org/superjson/-/superjson-2.2.6.tgz",
|
||||||
"integrity": "sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA==",
|
"integrity": "sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"copy-anything": "^4"
|
"copy-anything": "^4"
|
||||||
},
|
},
|
||||||
@@ -10992,9 +11282,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/typescript": {
|
"node_modules/typescript": {
|
||||||
"version": "6.0.2",
|
"version": "6.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz",
|
||||||
"integrity": "sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ==",
|
"integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"peer": true,
|
"peer": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
@@ -11006,9 +11296,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/ufo": {
|
"node_modules/ufo": {
|
||||||
"version": "1.6.3",
|
"version": "1.6.4",
|
||||||
"resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.3.tgz",
|
"resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.4.tgz",
|
||||||
"integrity": "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==",
|
"integrity": "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/ultrahtml": {
|
"node_modules/ultrahtml": {
|
||||||
@@ -11406,6 +11696,85 @@
|
|||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/vee-validate": {
|
||||||
|
"version": "4.15.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/vee-validate/-/vee-validate-4.15.1.tgz",
|
||||||
|
"integrity": "sha512-DkFsiTwEKau8VIxyZBGdO6tOudD+QoUBPuHj3e6QFqmbfCRj1ArmYWue9lEp6jLSWBIw4XPlDLjFIZNLdRAMSg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@vue/devtools-api": "^7.5.2",
|
||||||
|
"type-fest": "^4.8.3"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"vue": "^3.4.26"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vee-validate/node_modules/@vue/devtools-api": {
|
||||||
|
"version": "7.7.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-7.7.9.tgz",
|
||||||
|
"integrity": "sha512-kIE8wvwlcZ6TJTbNeU2HQNtaxLx3a84aotTITUuL/4bzfPxzajGBOoqjMhwZJ8L9qFYDU/lAYMEEm11dnZOD6g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@vue/devtools-kit": "^7.7.9"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vee-validate/node_modules/@vue/devtools-kit": {
|
||||||
|
"version": "7.7.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.7.9.tgz",
|
||||||
|
"integrity": "sha512-PyQ6odHSgiDVd4hnTP+aDk2X4gl2HmLDfiyEnn3/oV+ckFDuswRs4IbBT7vacMuGdwY/XemxBoh302ctbsptuA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@vue/devtools-shared": "^7.7.9",
|
||||||
|
"birpc": "^2.3.0",
|
||||||
|
"hookable": "^5.5.3",
|
||||||
|
"mitt": "^3.0.1",
|
||||||
|
"perfect-debounce": "^1.0.0",
|
||||||
|
"speakingurl": "^14.0.1",
|
||||||
|
"superjson": "^2.2.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vee-validate/node_modules/@vue/devtools-shared": {
|
||||||
|
"version": "7.7.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.7.9.tgz",
|
||||||
|
"integrity": "sha512-iWAb0v2WYf0QWmxCGy0seZNDPdO3Sp5+u78ORnyeonS6MT4PC7VPrryX2BpMJrwlDeaZ6BD4vP4XKjK0SZqaeA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"rfdc": "^1.4.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vee-validate/node_modules/birpc": {
|
||||||
|
"version": "2.9.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/birpc/-/birpc-2.9.0.tgz",
|
||||||
|
"integrity": "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/antfu"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vee-validate/node_modules/hookable": {
|
||||||
|
"version": "5.5.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz",
|
||||||
|
"integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/vee-validate/node_modules/perfect-debounce": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/vee-validate/node_modules/type-fest": {
|
||||||
|
"version": "4.41.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz",
|
||||||
|
"integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==",
|
||||||
|
"license": "(MIT OR CC0-1.0)",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/vite": {
|
"node_modules/vite": {
|
||||||
"version": "7.3.2",
|
"version": "7.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-7.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-7.3.2.tgz",
|
||||||
@@ -11851,6 +12220,28 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/vue-sonner": {
|
||||||
|
"version": "2.0.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/vue-sonner/-/vue-sonner-2.0.9.tgz",
|
||||||
|
"integrity": "sha512-i6BokNlNDL93fpzNxN/LZSn6D6MzlO+i3qXt6iVZne3x1k7R46d5HlFB4P8tYydhgqOrRbIZEsnRd3kG7qGXyw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@nuxt/kit": "^4.0.3",
|
||||||
|
"@nuxt/schema": "^4.0.3",
|
||||||
|
"nuxt": "^4.0.3"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@nuxt/kit": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@nuxt/schema": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"nuxt": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/webidl-conversions": {
|
"node_modules/webidl-conversions": {
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
||||||
@@ -12129,6 +12520,15 @@
|
|||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 14"
|
"node": ">= 14"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"node_modules/zod": {
|
||||||
|
"version": "3.25.76",
|
||||||
|
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
|
||||||
|
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/colinhacks"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
14
package.json
14
package.json
@@ -5,6 +5,8 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "nuxt build",
|
"build": "nuxt build",
|
||||||
"dev": "nuxt dev",
|
"dev": "nuxt dev",
|
||||||
|
"format": "prettier . --write",
|
||||||
|
"format:check": "prettier . --check",
|
||||||
"generate": "nuxt generate",
|
"generate": "nuxt generate",
|
||||||
"preview": "nuxt preview",
|
"preview": "nuxt preview",
|
||||||
"postinstall": "nuxt prepare",
|
"postinstall": "nuxt prepare",
|
||||||
@@ -17,17 +19,25 @@
|
|||||||
"seed": "node prisma/seed.mjs"
|
"seed": "node prisma/seed.mjs"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@nuxt/icon": "^2.2.1",
|
||||||
"@nuxtjs/tailwindcss": "^6.14.0",
|
"@nuxtjs/tailwindcss": "^6.14.0",
|
||||||
"@pinia/nuxt": "^0.11.3",
|
"@pinia/nuxt": "^0.11.3",
|
||||||
"@prisma/client": "^6.16.2",
|
"@prisma/client": "^6.16.2",
|
||||||
"@prisma/nuxt": "^0.3.0",
|
"@prisma/nuxt": "^0.3.0",
|
||||||
|
"@vee-validate/nuxt": "^4.15.1",
|
||||||
|
"@vee-validate/zod": "^4.15.1",
|
||||||
"jose": "^6.2.2",
|
"jose": "^6.2.2",
|
||||||
"nuxt": "^4.4.2",
|
"nuxt": "^4.4.2",
|
||||||
|
"pinia": "^3.0.4",
|
||||||
"prisma": "^6.16.2",
|
"prisma": "^6.16.2",
|
||||||
"vue": "^3.5.32",
|
"vue": "^3.5.32",
|
||||||
"vue-router": "^5.0.4"
|
"vue-router": "^5.0.4",
|
||||||
|
"vue-sonner": "^2.0.9",
|
||||||
|
"zod": "^3.25.76"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^25.6.0"
|
"@types/node": "^25.6.0",
|
||||||
|
"prettier": "^3.8.3",
|
||||||
|
"prettier-plugin-tailwindcss": "^0.8.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,12 +63,7 @@
|
|||||||
"example": "2026-04-28T12:00:00.000Z"
|
"example": "2026-04-28T12:00:00.000Z"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": ["id", "email", "created_at", "updated_at"]
|
||||||
"id",
|
|
||||||
"email",
|
|
||||||
"created_at",
|
|
||||||
"updated_at"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"TokenResponse": {
|
"TokenResponse": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
@@ -93,12 +88,7 @@
|
|||||||
"example": 900
|
"example": 900
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": ["access_token", "refresh_token", "token_type", "expires_in"]
|
||||||
"access_token",
|
|
||||||
"refresh_token",
|
|
||||||
"token_type",
|
|
||||||
"expires_in"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"ErrorResponse": {
|
"ErrorResponse": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
@@ -112,19 +102,14 @@
|
|||||||
"example": "Email é obrigatório"
|
"example": "Email é obrigatório"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": ["statusCode", "message"]
|
||||||
"statusCode",
|
|
||||||
"message"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"paths": {
|
"paths": {
|
||||||
"/api/auth/register": {
|
"/api/auth/register": {
|
||||||
"post": {
|
"post": {
|
||||||
"tags": [
|
"tags": ["Auth"],
|
||||||
"Auth"
|
|
||||||
],
|
|
||||||
"summary": "Registrar novo usuário",
|
"summary": "Registrar novo usuário",
|
||||||
"description": "Cria uma conta nova. A senha é armazenada como hash scrypt — nunca em plaintext.",
|
"description": "Cria uma conta nova. A senha é armazenada como hash scrypt — nunca em plaintext.",
|
||||||
"operationId": "authRegister",
|
"operationId": "authRegister",
|
||||||
@@ -134,10 +119,7 @@
|
|||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": ["email", "password"],
|
||||||
"email",
|
|
||||||
"password"
|
|
||||||
],
|
|
||||||
"properties": {
|
"properties": {
|
||||||
"email": {
|
"email": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
@@ -200,9 +182,7 @@
|
|||||||
},
|
},
|
||||||
"/api/auth/login": {
|
"/api/auth/login": {
|
||||||
"post": {
|
"post": {
|
||||||
"tags": [
|
"tags": ["Auth"],
|
||||||
"Auth"
|
|
||||||
],
|
|
||||||
"summary": "Login",
|
"summary": "Login",
|
||||||
"description": "Autentica o usuário e retorna um par de tokens (access + refresh).",
|
"description": "Autentica o usuário e retorna um par de tokens (access + refresh).",
|
||||||
"operationId": "authLogin",
|
"operationId": "authLogin",
|
||||||
@@ -212,10 +192,7 @@
|
|||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": ["email", "password"],
|
||||||
"email",
|
|
||||||
"password"
|
|
||||||
],
|
|
||||||
"properties": {
|
"properties": {
|
||||||
"email": {
|
"email": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
@@ -271,9 +248,7 @@
|
|||||||
},
|
},
|
||||||
"/api/auth/refresh": {
|
"/api/auth/refresh": {
|
||||||
"post": {
|
"post": {
|
||||||
"tags": [
|
"tags": ["Auth"],
|
||||||
"Auth"
|
|
||||||
],
|
|
||||||
"summary": "Renovar tokens",
|
"summary": "Renovar tokens",
|
||||||
"description": "Troca um refresh token válido por um novo par de tokens. O token antigo é revogado imediatamente (rotação).",
|
"description": "Troca um refresh token válido por um novo par de tokens. O token antigo é revogado imediatamente (rotação).",
|
||||||
"operationId": "authRefresh",
|
"operationId": "authRefresh",
|
||||||
@@ -283,9 +258,7 @@
|
|||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": ["refresh_token"],
|
||||||
"refresh_token"
|
|
||||||
],
|
|
||||||
"properties": {
|
"properties": {
|
||||||
"refresh_token": {
|
"refresh_token": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
@@ -337,9 +310,7 @@
|
|||||||
},
|
},
|
||||||
"/api/auth/forgot-password": {
|
"/api/auth/forgot-password": {
|
||||||
"post": {
|
"post": {
|
||||||
"tags": [
|
"tags": ["Auth"],
|
||||||
"Auth"
|
|
||||||
],
|
|
||||||
"summary": "Solicitar reset de senha",
|
"summary": "Solicitar reset de senha",
|
||||||
"description": "Gera um token de reset para o email informado. A resposta é sempre genérica para não revelar se o email existe (proteção contra enumeração). Em produção, o token seria enviado por email; aqui é retornado no body para fins de teste.",
|
"description": "Gera um token de reset para o email informado. A resposta é sempre genérica para não revelar se o email existe (proteção contra enumeração). Em produção, o token seria enviado por email; aqui é retornado no body para fins de teste.",
|
||||||
"operationId": "authForgotPassword",
|
"operationId": "authForgotPassword",
|
||||||
@@ -349,9 +320,7 @@
|
|||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": ["email"],
|
||||||
"email"
|
|
||||||
],
|
|
||||||
"properties": {
|
"properties": {
|
||||||
"email": {
|
"email": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
@@ -414,9 +383,7 @@
|
|||||||
},
|
},
|
||||||
"/api/auth/reset-password": {
|
"/api/auth/reset-password": {
|
||||||
"post": {
|
"post": {
|
||||||
"tags": [
|
"tags": ["Auth"],
|
||||||
"Auth"
|
|
||||||
],
|
|
||||||
"summary": "Redefinir senha",
|
"summary": "Redefinir senha",
|
||||||
"description": "Aplica a nova senha usando o token de reset. O token é de uso único — após utilizado, é marcado como consumido.",
|
"description": "Aplica a nova senha usando o token de reset. O token é de uso único — após utilizado, é marcado como consumido.",
|
||||||
"operationId": "authResetPassword",
|
"operationId": "authResetPassword",
|
||||||
@@ -426,10 +393,7 @@
|
|||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": ["token", "new_password"],
|
||||||
"token",
|
|
||||||
"new_password"
|
|
||||||
],
|
|
||||||
"properties": {
|
"properties": {
|
||||||
"token": {
|
"token": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
@@ -493,9 +457,7 @@
|
|||||||
},
|
},
|
||||||
"/profile/me": {
|
"/profile/me": {
|
||||||
"get": {
|
"get": {
|
||||||
"tags": [
|
"tags": ["Profile"],
|
||||||
"Profile"
|
|
||||||
],
|
|
||||||
"summary": "Perfil do usuário autenticado",
|
"summary": "Perfil do usuário autenticado",
|
||||||
"description": "Retorna os dados do usuário identificado pelo access token JWT.",
|
"description": "Retorna os dados do usuário identificado pelo access token JWT.",
|
||||||
"operationId": "profileMe",
|
"operationId": "profileMe",
|
||||||
@@ -544,9 +506,7 @@
|
|||||||
},
|
},
|
||||||
"/dashboard": {
|
"/dashboard": {
|
||||||
"get": {
|
"get": {
|
||||||
"tags": [
|
"tags": ["Dashboard"],
|
||||||
"Dashboard"
|
|
||||||
],
|
|
||||||
"summary": "Dashboard (orquestração A→B)",
|
"summary": "Dashboard (orquestração A→B)",
|
||||||
"description": "Endpoint protegido que demonstra orquestração entre serviços: chama /profile/me internamente e agrega os resultados de duas APIs (A e B), verificando que o `sub` do JWT é consistente entre elas.",
|
"description": "Endpoint protegido que demonstra orquestração entre serviços: chama /profile/me internamente e agrega os resultados de duas APIs (A e B), verificando que o `sub` do JWT é consistente entre elas.",
|
||||||
"operationId": "dashboard",
|
"operationId": "dashboard",
|
||||||
@@ -599,4 +559,4 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -92,7 +92,10 @@ export function getAuthRuntimeConfig(event?: H3Event): AuthRuntimeConfig {
|
|||||||
kid,
|
kid,
|
||||||
accessTtlSec: parsePositiveInt(String(runtimeConfig.jwtAccessTtlSec), 'JWT access TTL'),
|
accessTtlSec: parsePositiveInt(String(runtimeConfig.jwtAccessTtlSec), 'JWT access TTL'),
|
||||||
refreshTtlSec: parsePositiveInt(String(runtimeConfig.jwtRefreshTtlSec), 'JWT refresh TTL'),
|
refreshTtlSec: parsePositiveInt(String(runtimeConfig.jwtRefreshTtlSec), 'JWT refresh TTL'),
|
||||||
passwordResetTtlSec: parsePositiveInt(String(runtimeConfig.passwordResetTtlSec), 'Password reset TTL'),
|
passwordResetTtlSec: parsePositiveInt(
|
||||||
|
String(runtimeConfig.passwordResetTtlSec),
|
||||||
|
'Password reset TTL'
|
||||||
|
),
|
||||||
privateKeyPem: normalizePem(String(runtimeConfig.jwtPrivateKeyPem ?? ''), 'JWT private key'),
|
privateKeyPem: normalizePem(String(runtimeConfig.jwtPrivateKeyPem ?? ''), 'JWT private key'),
|
||||||
publicKeyPem: normalizePem(String(runtimeConfig.jwtPublicKeyPem ?? ''), 'JWT public key'),
|
publicKeyPem: normalizePem(String(runtimeConfig.jwtPublicKeyPem ?? ''), 'JWT public key'),
|
||||||
refreshTokenPepper,
|
refreshTokenPepper,
|
||||||
|
|||||||
@@ -37,6 +37,8 @@ export function getRouteRequirement(method: string, path: string): AuthRouteRequ
|
|||||||
const normalizedPath = normalizePath(path)
|
const normalizedPath = normalizePath(path)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
PROTECTED_ROUTES.find((route) => route.method === normalizedMethod && route.path === normalizedPath) ?? null
|
PROTECTED_ROUTES.find(
|
||||||
|
(route) => route.method === normalizedMethod && route.path === normalizedPath
|
||||||
|
) ?? null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -326,7 +326,7 @@ export async function handleLogin(event: H3Event) {
|
|||||||
const user = await prisma.user.findUnique({ where: { email } })
|
const user = await prisma.user.findUnique({ where: { email } })
|
||||||
|
|
||||||
if (!user || !verifyPassword(password, user.passwordHash)) {
|
if (!user || !verifyPassword(password, user.passwordHash)) {
|
||||||
throw createError({ statusCode: 401, statusMessage: 'Invalid credentials' })
|
throw createError({ statusCode: 401, statusMessage: 'Credenciais inválidas!' })
|
||||||
}
|
}
|
||||||
|
|
||||||
const accessToken = await signAccessToken(event, { sub: user.id })
|
const accessToken = await signAccessToken(event, { sub: user.id })
|
||||||
|
|||||||
@@ -1,11 +1,5 @@
|
|||||||
import { createError, type H3Event } from 'h3'
|
import { createError, type H3Event } from 'h3'
|
||||||
import {
|
import { SignJWT, importPKCS8, importSPKI, jwtVerify, type JWTPayload } from 'jose'
|
||||||
SignJWT,
|
|
||||||
importPKCS8,
|
|
||||||
importSPKI,
|
|
||||||
jwtVerify,
|
|
||||||
type JWTPayload
|
|
||||||
} from 'jose'
|
|
||||||
|
|
||||||
import type { AccessTokenClaims } from '../types/auth'
|
import type { AccessTokenClaims } from '../types/auth'
|
||||||
import { getAuthRuntimeConfig } from './auth-config'
|
import { getAuthRuntimeConfig } from './auth-config'
|
||||||
@@ -74,7 +68,10 @@ async function loadSigningKeys(event: H3Event): Promise<LoadedSigningKeys> {
|
|||||||
* @param payload Dados mínimos do token (apenas `sub`).
|
* @param payload Dados mínimos do token (apenas `sub`).
|
||||||
* @returns JWT assinado em formato string.
|
* @returns JWT assinado em formato string.
|
||||||
*/
|
*/
|
||||||
export async function signAccessToken(event: H3Event, payload: Pick<AccessTokenClaims, 'sub'>): Promise<string> {
|
export async function signAccessToken(
|
||||||
|
event: H3Event,
|
||||||
|
payload: Pick<AccessTokenClaims, 'sub'>
|
||||||
|
): Promise<string> {
|
||||||
const config = getAuthRuntimeConfig(event)
|
const config = getAuthRuntimeConfig(event)
|
||||||
const keys = await loadSigningKeys(event)
|
const keys = await loadSigningKeys(event)
|
||||||
|
|
||||||
|
|||||||
@@ -39,7 +39,10 @@ function generateRawRefreshToken(): string {
|
|||||||
* @param userId ID do usuário dono do token.
|
* @param userId ID do usuário dono do token.
|
||||||
* @returns Metadados do refresh token emitido.
|
* @returns Metadados do refresh token emitido.
|
||||||
*/
|
*/
|
||||||
export async function issueRefreshToken(event: H3Event, userId: string): Promise<IssuedRefreshToken> {
|
export async function issueRefreshToken(
|
||||||
|
event: H3Event,
|
||||||
|
userId: string
|
||||||
|
): Promise<IssuedRefreshToken> {
|
||||||
const config = getAuthRuntimeConfig(event)
|
const config = getAuthRuntimeConfig(event)
|
||||||
const token = generateRawRefreshToken()
|
const token = generateRawRefreshToken()
|
||||||
const tokenHash = hashRefreshToken(token, config.refreshTokenPepper)
|
const tokenHash = hashRefreshToken(token, config.refreshTokenPepper)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
{
|
{
|
||||||
// https://nuxt.com/docs/guide/concepts/typescript
|
// https://nuxt.com/docs/guide/concepts/typescript
|
||||||
"extends": "./.nuxt/tsconfig.json"
|
"extends": "./.nuxt/tsconfig.json"
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user