Compare commits

...

20 Commits

Author SHA1 Message Date
2b5f784d61 refac: move rotas protegidas para dentro de (protected) 2026-05-04 18:34:47 -05:00
f493a6f674 feat: adiciona navegacao entre criar conta e fazer login 2026-05-04 18:28:13 -05:00
9b76618b71 refac: aprimora logica de criar conta do usuario e padroniza requisicao com $fetch 2026-05-04 18:21:54 -05:00
b365fca7eb style: garante que todos $toast tenham duracao de 8s 2026-05-04 18:17:47 -05:00
35f32578e3 refac: cria middleware guest e melhora logica de login 2026-05-04 18:14:41 -05:00
b8dbb5869d login redirecionando pra home e acessando home se tiver logado 2026-05-03 13:03:14 -05:00
404e2d457b login funcionando 2026-05-03 10:54:48 -05:00
ed179f498a iniciando página de login 2026-05-02 21:21:29 -05:00
cf80e63255 feat: implementa feedback de carregamento ao enviar form em criar conta 2026-04-29 23:11:32 -05:00
b6f7d5e8a0 chore: instala e configura @nuxt/icon 2026-04-29 23:11:01 -05:00
1b3910a001 feat: implementa notificacao na rota de criar conta 2026-04-29 23:00:48 -05:00
bb8a3c6f83 chore: instala e configura toast com vue-sonner 2026-04-29 23:00:18 -05:00
1acde7202e feat: adiciona validacao de formulario na criacao de conta 2026-04-29 22:38:59 -05:00
3e688c1d0f chore: instala e configura vee-validate e zod para validacao de forms 2026-04-29 22:38:16 -05:00
f879e7b3f8 style: atualiza design da rota de criacao de conta 2026-04-29 22:21:26 -05:00
5dc80943fa chore: atualiza configuracoes do editor (ide) e extensoes recomendadas 2026-04-29 22:20:46 -05:00
c323d4d6af chore: configura o prettier para formatacao do codigo 2026-04-29 20:03:32 -05:00
6c4a7c5116 Merge branch 'main' of https://git.juancjc.com.br/antonioandre/nuxt-frontend 2026-04-29 19:24:20 -05:00
5a9aa57d83 Merge branch 'main' of https://git.juancjc.com.br/antonioandre/nuxt-frontend into main 2026-04-28 20:06:31 -05:00
7354c28a8e wip: implementando tela de cadastro do usuário 2026-04-27 20:24:34 -05:00
23 changed files with 858 additions and 222 deletions

9
.prettierignore Normal file
View File

@@ -0,0 +1,9 @@
node_modules
.nuxt
.output
.data
.cache
dist
coverage
.codex
package-lock.json

8
.prettierrc Normal file
View 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
View File

@@ -0,0 +1,3 @@
{
"recommendations": ["esbenp.prettier-vscode", "Vue.volar"]
}

19
.vscode/settings.json vendored Normal file
View 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
}

View File

@@ -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:
@@ -76,7 +76,7 @@ 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 |

View File

@@ -1,6 +1,6 @@
<template> <template>
<div> <NuxtLayout>
<NuxtRouteAnnouncer /> <NuxtPage />
<NuxtWelcome /> </NuxtLayout>
</div> <Toaster position="top-right" rich-colors />
</template> </template>

7
app/middleware/auth.ts Normal file
View 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
View 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')
}
})

View File

@@ -0,0 +1,120 @@
<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(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]">
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>
<script setup>
import { toTypedSchema } from '@vee-validate/zod'
import { z } from 'zod'
definePageMeta({ middleware: 'guest' })
const { $toast } = useNuxtApp()
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(8, 'A senha deve ter no mínimo 8 caracteres')),
}),
)
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>

View 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>

View File

@@ -0,0 +1,11 @@
<template>
<div>
<h1>OOOIII SOU A HOMEEE</h1>
</div>
</template>
<script setup>
definePageMeta({
middleware: 'auth'
})
</script>

5
app/pages/index.vue Normal file
View File

@@ -0,0 +1,5 @@
<template>
<div>
<h1>Welcome to the Home Page</h1>
</div>
</template>

View File

@@ -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()

View File

@@ -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',
]
} }
} }
}) })

591
package-lock.json generated
View File

@@ -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",
@@ -61,7 +82,6 @@
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz",
"integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@babel/code-frame": "^7.29.0", "@babel/code-frame": "^7.29.0",
"@babel/generator": "^7.29.0", "@babel/generator": "^7.29.0",
@@ -559,12 +579,36 @@
"integrity": "sha512-/B8YJGPzaYq1NbsQmwgP8EZqg40NpTw4ZB3suuI0TplbxKHeK94jeaawLmVhCv+YwUnOpiWEz9U6SeThku/8JQ==", "integrity": "sha512-/B8YJGPzaYq1NbsQmwgP8EZqg40NpTw4ZB3suuI0TplbxKHeK94jeaawLmVhCv+YwUnOpiWEz9U6SeThku/8JQ==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/@emnapi/core": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz",
"integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==",
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"@emnapi/wasi-threads": "1.2.1",
"tslib": "^2.4.0"
}
},
"node_modules/@emnapi/runtime": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz",
"integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==",
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@emnapi/wasi-threads": { "node_modules/@emnapi/wasi-threads": {
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz",
"integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==",
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"peer": true,
"dependencies": { "dependencies": {
"tslib": "^2.4.0" "tslib": "^2.4.0"
} }
@@ -985,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",
@@ -1370,12 +1455,39 @@
"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",
"integrity": "sha512-5+IPRNX2CjkBhuWUwz0hBuLqiaJPRoKzQ+SvcdrQDbAyE+VDeFt74VpSFr5/R0ujrK4b+XnSHUJWdS72w6hsog==", "integrity": "sha512-5+IPRNX2CjkBhuWUwz0hBuLqiaJPRoKzQ+SvcdrQDbAyE+VDeFt74VpSFr5/R0ujrK4b+XnSHUJWdS72w6hsog==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"c12": "^3.3.3", "c12": "^3.3.3",
"consola": "^3.4.2", "consola": "^3.4.2",
@@ -1460,7 +1572,6 @@
"resolved": "https://registry.npmjs.org/@nuxt/schema/-/schema-4.4.2.tgz", "resolved": "https://registry.npmjs.org/@nuxt/schema/-/schema-4.4.2.tgz",
"integrity": "sha512-/q6C7Qhiricgi+PKR7ovBnJlKTL0memCbA1CzRT+itCW/oeYzUfeMdQ35mGntlBoyRPNrMXbzuSUhfDbSCU57w==", "integrity": "sha512-/q6C7Qhiricgi+PKR7ovBnJlKTL0memCbA1CzRT+itCW/oeYzUfeMdQ35mGntlBoyRPNrMXbzuSUhfDbSCU57w==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@vue/shared": "^3.5.30", "@vue/shared": "^3.5.30",
"defu": "^6.1.4", "defu": "^6.1.4",
@@ -3079,6 +3190,19 @@
"giget": "dist/cli.mjs" "giget": "dist/cli.mjs"
} }
}, },
"node_modules/@prisma/config/node_modules/magicast": {
"version": "0.3.5",
"resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz",
"integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==",
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"@babel/parser": "^7.25.4",
"@babel/types": "^7.25.4",
"source-map-js": "^1.2.0"
}
},
"node_modules/@prisma/config/node_modules/perfect-debounce": { "node_modules/@prisma/config/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",
@@ -3884,7 +4008,6 @@
"integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==", "integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==",
"devOptional": true, "devOptional": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"undici-types": "~7.19.0" "undici-types": "~7.19.0"
} }
@@ -3911,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",
@@ -4265,7 +4500,6 @@
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
"integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
"license": "MIT", "license": "MIT",
"peer": true,
"bin": { "bin": {
"acorn": "bin/acorn" "acorn": "bin/acorn"
}, },
@@ -4571,16 +4805,6 @@
"postcss": "^8.1.0" "postcss": "^8.1.0"
} }
}, },
"node_modules/aws-ssl-profiles": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz",
"integrity": "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==",
"license": "MIT",
"optional": true,
"engines": {
"node": ">= 6.0.0"
}
},
"node_modules/b4a": { "node_modules/b4a": {
"version": "1.8.0", "version": "1.8.0",
"resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.0.tgz", "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.0.tgz",
@@ -4609,7 +4833,6 @@
"resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz", "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz",
"integrity": "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==", "integrity": "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==",
"license": "Apache-2.0", "license": "Apache-2.0",
"peer": true,
"peerDependencies": { "peerDependencies": {
"bare-abort-controller": "*" "bare-abort-controller": "*"
}, },
@@ -4807,7 +5030,6 @@
} }
], ],
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"baseline-browser-mapping": "^2.10.12", "baseline-browser-mapping": "^2.10.12",
"caniuse-lite": "^1.0.30001782", "caniuse-lite": "^1.0.30001782",
@@ -4909,7 +5131,6 @@
"resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz",
"integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==",
"license": "MIT", "license": "MIT",
"peer": true,
"engines": { "engines": {
"node": ">=8" "node": ">=8"
} }
@@ -5089,8 +5310,7 @@
"version": "0.2.2", "version": "0.2.2",
"resolved": "https://registry.npmjs.org/citty/-/citty-0.2.2.tgz", "resolved": "https://registry.npmjs.org/citty/-/citty-0.2.2.tgz",
"integrity": "sha512-+6vJA3L98yv+IdfKGZHBNiGW5KHn22e/JwID0Strsz8h4S/csAu/OuICwxrg44k5MRiZHWIo8XXuJgQTriRP4w==", "integrity": "sha512-+6vJA3L98yv+IdfKGZHBNiGW5KHn22e/JwID0Strsz8h4S/csAu/OuICwxrg44k5MRiZHWIo8XXuJgQTriRP4w==",
"license": "MIT", "license": "MIT"
"peer": true
}, },
"node_modules/cliui": { "node_modules/cliui": {
"version": "9.0.1", "version": "9.0.1",
@@ -6329,16 +6549,6 @@
"integrity": "sha512-Tt4kuxLXFKHy8KT40zwsUPUkg1CrsgY25FxA2U/j/0WgEDCk3ddc/zLTCCcbSHX9FcKtLuVaDGtGE/STWC+j3Q==", "integrity": "sha512-Tt4kuxLXFKHy8KT40zwsUPUkg1CrsgY25FxA2U/j/0WgEDCk3ddc/zLTCCcbSHX9FcKtLuVaDGtGE/STWC+j3Q==",
"license": "BSD-3-Clause" "license": "BSD-3-Clause"
}, },
"node_modules/generate-function": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz",
"integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==",
"license": "MIT",
"optional": true,
"dependencies": {
"is-property": "^1.0.2"
}
},
"node_modules/generator-function": { "node_modules/generator-function": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz",
@@ -6721,23 +6931,6 @@
"node": ">=16.17.0" "node": ">=16.17.0"
} }
}, },
"node_modules/iconv-lite": {
"version": "0.7.2",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz",
"integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==",
"license": "MIT",
"optional": true,
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3.0.0"
},
"engines": {
"node": ">=0.10.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/ieee754": { "node_modules/ieee754": {
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
@@ -7009,13 +7202,6 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/is-property": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz",
"integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==",
"license": "MIT",
"optional": true
},
"node_modules/is-reference": { "node_modules/is-reference": {
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz",
@@ -7530,13 +7716,6 @@
"integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/long": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz",
"integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==",
"license": "Apache-2.0",
"optional": true
},
"node_modules/lru-cache": { "node_modules/lru-cache": {
"version": "5.1.1", "version": "5.1.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
@@ -7546,22 +7725,6 @@
"yallist": "^3.0.2" "yallist": "^3.0.2"
} }
}, },
"node_modules/lru.min": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.4.tgz",
"integrity": "sha512-DqC6n3QQ77zdFpCMASA1a3Jlb64Hv2N2DciFGkO/4L9+q/IpIAuRlKOvCXabtRW6cQf8usbmM6BE/TOPysCdIA==",
"license": "MIT",
"optional": true,
"engines": {
"bun": ">=1.0.0",
"deno": ">=1.30.0",
"node": ">=8.0.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wellwelwel"
}
},
"node_modules/magic-regexp": { "node_modules/magic-regexp": {
"version": "0.10.0", "version": "0.10.0",
"resolved": "https://registry.npmjs.org/magic-regexp/-/magic-regexp-0.10.0.tgz", "resolved": "https://registry.npmjs.org/magic-regexp/-/magic-regexp-0.10.0.tgz",
@@ -7870,19 +8033,6 @@
"thenify-all": "^1.0.0" "thenify-all": "^1.0.0"
} }
}, },
"node_modules/named-placeholders": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.6.tgz",
"integrity": "sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w==",
"license": "MIT",
"optional": true,
"dependencies": {
"lru.min": "^1.1.0"
},
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/nanoid": { "node_modules/nanoid": {
"version": "3.3.11", "version": "3.3.11",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
@@ -8147,7 +8297,6 @@
"resolved": "https://registry.npmjs.org/nuxt/-/nuxt-4.4.2.tgz", "resolved": "https://registry.npmjs.org/nuxt/-/nuxt-4.4.2.tgz",
"integrity": "sha512-iWVFpr/YEqVU/CenqIHMnIkvb2HE/9f+q8oxZ+pj2et+60NljGRClCgnmbvGPdmNFE0F1bEhoBCYfqbDOCim3Q==", "integrity": "sha512-iWVFpr/YEqVU/CenqIHMnIkvb2HE/9f+q8oxZ+pj2et+60NljGRClCgnmbvGPdmNFE0F1bEhoBCYfqbDOCim3Q==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@dxup/nuxt": "^0.4.0", "@dxup/nuxt": "^0.4.0",
"@nuxt/cli": "^3.34.0", "@nuxt/cli": "^3.34.0",
@@ -8400,7 +8549,6 @@
"resolved": "https://registry.npmjs.org/oxc-parser/-/oxc-parser-0.117.0.tgz", "resolved": "https://registry.npmjs.org/oxc-parser/-/oxc-parser-0.117.0.tgz",
"integrity": "sha512-l3cbgK5wUvWDVNWM/JFU77qDdGZK1wudnLsFcrRyNo/bL1CyU8pC25vDhMHikVY29lbK2InTWsX42RxVSutUdQ==", "integrity": "sha512-l3cbgK5wUvWDVNWM/JFU77qDdGZK1wudnLsFcrRyNo/bL1CyU8pC25vDhMHikVY29lbK2InTWsX42RxVSutUdQ==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@oxc-project/types": "^0.117.0" "@oxc-project/types": "^0.117.0"
}, },
@@ -8485,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",
@@ -8593,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"
}, },
@@ -8716,7 +8869,6 @@
} }
], ],
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"nanoid": "^3.3.11", "nanoid": "^3.3.11",
"picocolors": "^1.1.1", "picocolors": "^1.1.1",
@@ -9260,7 +9412,6 @@
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz",
"integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"cssesc": "^3.0.0", "cssesc": "^3.0.0",
"util-deprecate": "^1.0.2" "util-deprecate": "^1.0.2"
@@ -9318,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",
@@ -9336,7 +9582,6 @@
"integrity": "sha512-aRvldGE5UUJTtVmFiH3WfNFNiqFlAtePUxcI0UEGlnXCX7DqhiMT5TRYwncHFeA/Reca5W6ToXXyCMTeFPdSXA==", "integrity": "sha512-aRvldGE5UUJTtVmFiH3WfNFNiqFlAtePUxcI0UEGlnXCX7DqhiMT5TRYwncHFeA/Reca5W6ToXXyCMTeFPdSXA==",
"hasInstallScript": true, "hasInstallScript": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"peer": true,
"dependencies": { "dependencies": {
"@prisma/config": "6.16.2", "@prisma/config": "6.16.2",
"@prisma/engines": "6.16.2" "@prisma/engines": "6.16.2"
@@ -9850,7 +10095,6 @@
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.1.tgz", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.1.tgz",
"integrity": "sha512-VmtB2rFU/GroZ4oL8+ZqXgSA38O6GR8KSIvWmEFv63pQ0G6KaBH9s07PO8XTXP4vI+3UJUEypOfjkGfmSBBR0w==", "integrity": "sha512-VmtB2rFU/GroZ4oL8+ZqXgSA38O6GR8KSIvWmEFv63pQ0G6KaBH9s07PO8XTXP4vI+3UJUEypOfjkGfmSBBR0w==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@types/estree": "1.0.8" "@types/estree": "1.0.8"
}, },
@@ -9998,13 +10242,6 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"license": "MIT",
"optional": true
},
"node_modules/sax": { "node_modules/sax": {
"version": "1.6.0", "version": "1.6.0",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz", "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz",
@@ -10058,12 +10295,6 @@
"url": "https://opencollective.com/express" "url": "https://opencollective.com/express"
} }
}, },
"node_modules/seq-queue": {
"version": "0.0.5",
"resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz",
"integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==",
"optional": true
},
"node_modules/serialize-javascript": { "node_modules/serialize-javascript": {
"version": "7.0.5", "version": "7.0.5",
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-7.0.5.tgz", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-7.0.5.tgz",
@@ -10265,16 +10496,6 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/sqlstring": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz",
"integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==",
"license": "MIT",
"optional": true,
"engines": {
"node": ">= 0.6"
}
},
"node_modules/srvx": { "node_modules/srvx": {
"version": "0.11.15", "version": "0.11.15",
"resolved": "https://registry.npmjs.org/srvx/-/srvx-0.11.15.tgz", "resolved": "https://registry.npmjs.org/srvx/-/srvx-0.11.15.tgz",
@@ -10672,7 +10893,6 @@
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz",
"integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==", "integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@alloc/quick-lru": "^5.2.0", "@alloc/quick-lru": "^5.2.0",
"arg": "^5.0.2", "arg": "^5.0.2",
@@ -11062,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": {
@@ -11076,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": {
@@ -11476,12 +11696,90 @@
"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",
"integrity": "sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg==", "integrity": "sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"esbuild": "^0.27.0", "esbuild": "^0.27.0",
"fdir": "^6.5.0", "fdir": "^6.5.0",
@@ -11846,7 +12144,6 @@
"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.32.tgz", "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.32.tgz",
"integrity": "sha512-vM4z4Q9tTafVfMAK7IVzmxg34rSzTFMyIe0UUEijUCkn9+23lj0WRfA83dg7eQZIUlgOSGrkViIaCfqSAUXsMw==", "integrity": "sha512-vM4z4Q9tTafVfMAK7IVzmxg34rSzTFMyIe0UUEijUCkn9+23lj0WRfA83dg7eQZIUlgOSGrkViIaCfqSAUXsMw==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@vue/compiler-dom": "3.5.32", "@vue/compiler-dom": "3.5.32",
"@vue/compiler-sfc": "3.5.32", "@vue/compiler-sfc": "3.5.32",
@@ -11923,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",
@@ -12114,7 +12433,6 @@
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz",
"integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==", "integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==",
"license": "ISC", "license": "ISC",
"peer": true,
"bin": { "bin": {
"yaml": "bin.mjs" "yaml": "bin.mjs"
}, },
@@ -12202,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"
}
} }
} }
} }

View File

@@ -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"
} }
} }

View File

@@ -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",

View File

@@ -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,

View File

@@ -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
) )
} }

View File

@@ -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 })

View File

@@ -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)

View File

@@ -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)