diff --git a/.prettierignore b/.prettierignore
new file mode 100644
index 0000000..bdde5cf
--- /dev/null
+++ b/.prettierignore
@@ -0,0 +1,9 @@
+node_modules
+.nuxt
+.output
+.data
+.cache
+dist
+coverage
+.codex
+package-lock.json
diff --git a/.prettierrc b/.prettierrc
new file mode 100644
index 0000000..842ef3c
--- /dev/null
+++ b/.prettierrc
@@ -0,0 +1,8 @@
+{
+ "plugins": ["prettier-plugin-tailwindcss"],
+ "printWidth": 100,
+ "semi": false,
+ "singleQuote": true,
+ "tabWidth": 2,
+ "trailingComma": "none"
+}
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..1b8273f
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,19 @@
+{
+ "editor.defaultFormatter": "esbenp.prettier-vscode",
+ "editor.formatOnSave": true,
+ "[vue]": {
+ "editor.defaultFormatter": "esbenp.prettier-vscode"
+ },
+ "[typescript]": {
+ "editor.defaultFormatter": "esbenp.prettier-vscode"
+ },
+ "[javascript]": {
+ "editor.defaultFormatter": "esbenp.prettier-vscode"
+ },
+ "[json]": {
+ "editor.defaultFormatter": "esbenp.prettier-vscode"
+ },
+ "[jsonc]": {
+ "editor.defaultFormatter": "esbenp.prettier-vscode"
+ }
+}
diff --git a/README.md b/README.md
index 28020d2..a904f73 100644
--- a/README.md
+++ b/README.md
@@ -39,7 +39,7 @@ npm run jwt:keys
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.
-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:
@@ -75,15 +75,15 @@ A tabela `User` possui apenas:
## Endpoints
-| Método | Caminho | Auth | Descrição |
-|--------|---------|------|-----------|
-| POST | `/api/auth/register` | Não | Cria novo usuário |
-| POST | `/api/auth/login` | Não | Autentica e retorna tokens |
-| POST | `/api/auth/refresh` | Não | Rotaciona refresh token |
-| POST | `/api/auth/forgot-password` | Não | Solicita reset de senha |
-| POST | `/api/auth/reset-password` | Não | Aplica nova senha com token |
-| GET | `/profile/me` | Sim (Bearer) | Retorna usuário autenticado |
-| GET | `/dashboard` | Sim (Bearer) | Orquestração A→B |
+| Método | Caminho | Auth | Descrição |
+| ------ | --------------------------- | ------------ | --------------------------- |
+| POST | `/api/auth/register` | Não | Cria novo usuário |
+| POST | `/api/auth/login` | Não | Autentica e retorna tokens |
+| POST | `/api/auth/refresh` | Não | Rotaciona refresh token |
+| POST | `/api/auth/forgot-password` | Não | Solicita reset de senha |
+| POST | `/api/auth/reset-password` | Não | Aplica nova senha com token |
+| GET | `/profile/me` | Sim (Bearer) | Retorna usuário autenticado |
+| GET | `/dashboard` | Sim (Bearer) | Orquestração A→B |
## Guia para serviços consumidores
diff --git a/app/pages/(auth)/criar-conta/index.vue b/app/pages/(auth)/criar-conta/index.vue
index 8436e11..4a482b2 100644
--- a/app/pages/(auth)/criar-conta/index.vue
+++ b/app/pages/(auth)/criar-conta/index.vue
@@ -1,35 +1,117 @@
-
-
Oooiiii
- Email:
-
- Senha:
-
- Criar Conta
-
+
+
+
+
+ Nova conta
+
+
+ Crie sua conta
+
+
+ Um acesso simples para entrar no sistema com email, senha e uma experiencia mais calma
+ desde o primeiro clique.
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
+
diff --git a/app/pages/index.vue b/app/pages/index.vue
index 36f56fd..97ec497 100644
--- a/app/pages/index.vue
+++ b/app/pages/index.vue
@@ -1,5 +1,5 @@
-
-
Welcome to the Home Page
-
-
\ No newline at end of file
+
+
Welcome to the Home Page
+
+
diff --git a/lib/prisma.ts b/lib/prisma.ts
index 08a8751..7578cab 100644
--- a/lib/prisma.ts
+++ b/lib/prisma.ts
@@ -5,8 +5,8 @@ const prismaClientSingleton = () => {
}
declare const globalThis: {
- prismaGlobal: ReturnType;
-} & typeof global;
+ prismaGlobal: ReturnType
+} & typeof global
const prisma = globalThis.prismaGlobal ?? prismaClientSingleton()
diff --git a/package-lock.json b/package-lock.json
index 6542b15..587dcc9 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -18,7 +18,9 @@
"vue-router": "^5.0.4"
},
"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": {
@@ -61,6 +63,7 @@
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz",
"integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"@babel/code-frame": "^7.29.0",
"@babel/generator": "^7.29.0",
@@ -558,36 +561,12 @@
"integrity": "sha512-/B8YJGPzaYq1NbsQmwgP8EZqg40NpTw4ZB3suuI0TplbxKHeK94jeaawLmVhCv+YwUnOpiWEz9U6SeThku/8JQ==",
"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": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz",
"integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==",
"license": "MIT",
"optional": true,
- "peer": true,
"dependencies": {
"tslib": "^2.4.0"
}
@@ -1398,6 +1377,7 @@
"resolved": "https://registry.npmjs.org/@nuxt/kit/-/kit-4.4.2.tgz",
"integrity": "sha512-5+IPRNX2CjkBhuWUwz0hBuLqiaJPRoKzQ+SvcdrQDbAyE+VDeFt74VpSFr5/R0ujrK4b+XnSHUJWdS72w6hsog==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"c12": "^3.3.3",
"consola": "^3.4.2",
@@ -1482,6 +1462,7 @@
"resolved": "https://registry.npmjs.org/@nuxt/schema/-/schema-4.4.2.tgz",
"integrity": "sha512-/q6C7Qhiricgi+PKR7ovBnJlKTL0memCbA1CzRT+itCW/oeYzUfeMdQ35mGntlBoyRPNrMXbzuSUhfDbSCU57w==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"@vue/shared": "^3.5.30",
"defu": "^6.1.4",
@@ -3100,19 +3081,6 @@
"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": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz",
@@ -3918,6 +3886,7 @@
"integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==",
"devOptional": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"undici-types": "~7.19.0"
}
@@ -4298,6 +4267,7 @@
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
"integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
"license": "MIT",
+ "peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -4631,6 +4601,7 @@
"resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz",
"integrity": "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==",
"license": "Apache-2.0",
+ "peer": true,
"peerDependencies": {
"bare-abort-controller": "*"
},
@@ -4828,6 +4799,7 @@
}
],
"license": "MIT",
+ "peer": true,
"dependencies": {
"baseline-browser-mapping": "^2.10.12",
"caniuse-lite": "^1.0.30001782",
@@ -4929,6 +4901,7 @@
"resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz",
"integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==",
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=8"
}
@@ -5108,7 +5081,8 @@
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/citty/-/citty-0.2.2.tgz",
"integrity": "sha512-+6vJA3L98yv+IdfKGZHBNiGW5KHn22e/JwID0Strsz8h4S/csAu/OuICwxrg44k5MRiZHWIo8XXuJgQTriRP4w==",
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
},
"node_modules/cliui": {
"version": "9.0.1",
@@ -5273,7 +5247,6 @@
"resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-4.0.5.tgz",
"integrity": "sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==",
"license": "MIT",
- "peer": true,
"dependencies": {
"is-what": "^5.2.0"
},
@@ -7045,7 +7018,6 @@
"resolved": "https://registry.npmjs.org/is-what/-/is-what-5.5.0.tgz",
"integrity": "sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw==",
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=18"
},
@@ -7764,8 +7736,7 @@
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz",
"integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==",
- "license": "MIT",
- "peer": true
+ "license": "MIT"
},
"node_modules/mlly": {
"version": "1.8.2",
@@ -8098,6 +8069,7 @@
"resolved": "https://registry.npmjs.org/nuxt/-/nuxt-4.4.2.tgz",
"integrity": "sha512-iWVFpr/YEqVU/CenqIHMnIkvb2HE/9f+q8oxZ+pj2et+60NljGRClCgnmbvGPdmNFE0F1bEhoBCYfqbDOCim3Q==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"@dxup/nuxt": "^0.4.0",
"@nuxt/cli": "^3.34.0",
@@ -8350,6 +8322,7 @@
"resolved": "https://registry.npmjs.org/oxc-parser/-/oxc-parser-0.117.0.tgz",
"integrity": "sha512-l3cbgK5wUvWDVNWM/JFU77qDdGZK1wudnLsFcrRyNo/bL1CyU8pC25vDhMHikVY29lbK2InTWsX42RxVSutUdQ==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"@oxc-project/types": "^0.117.0"
},
@@ -8564,7 +8537,6 @@
"resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-7.7.9.tgz",
"integrity": "sha512-kIE8wvwlcZ6TJTbNeU2HQNtaxLx3a84aotTITUuL/4bzfPxzajGBOoqjMhwZJ8L9qFYDU/lAYMEEm11dnZOD6g==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@vue/devtools-kit": "^7.7.9"
}
@@ -8574,7 +8546,6 @@
"resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.7.9.tgz",
"integrity": "sha512-PyQ6odHSgiDVd4hnTP+aDk2X4gl2HmLDfiyEnn3/oV+ckFDuswRs4IbBT7vacMuGdwY/XemxBoh302ctbsptuA==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@vue/devtools-shared": "^7.7.9",
"birpc": "^2.3.0",
@@ -8590,7 +8561,6 @@
"resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.7.9.tgz",
"integrity": "sha512-iWAb0v2WYf0QWmxCGy0seZNDPdO3Sp5+u78ORnyeonS6MT4PC7VPrryX2BpMJrwlDeaZ6BD4vP4XKjK0SZqaeA==",
"license": "MIT",
- "peer": true,
"dependencies": {
"rfdc": "^1.4.1"
}
@@ -8600,7 +8570,6 @@
"resolved": "https://registry.npmjs.org/birpc/-/birpc-2.9.0.tgz",
"integrity": "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==",
"license": "MIT",
- "peer": true,
"funding": {
"url": "https://github.com/sponsors/antfu"
}
@@ -8609,15 +8578,13 @@
"version": "5.5.3",
"resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz",
"integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==",
- "license": "MIT",
- "peer": true
+ "license": "MIT"
},
"node_modules/pinia/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",
- "peer": true
+ "license": "MIT"
},
"node_modules/pirates": {
"version": "4.0.7",
@@ -8671,6 +8638,7 @@
}
],
"license": "MIT",
+ "peer": true,
"dependencies": {
"nanoid": "^3.3.11",
"picocolors": "^1.1.1",
@@ -9214,6 +9182,7 @@
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz",
"integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"cssesc": "^3.0.0",
"util-deprecate": "^1.0.2"
@@ -9271,6 +9240,102 @@
"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",
+ "peer": true,
+ "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": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-7.1.0.tgz",
@@ -9289,6 +9354,7 @@
"integrity": "sha512-aRvldGE5UUJTtVmFiH3WfNFNiqFlAtePUxcI0UEGlnXCX7DqhiMT5TRYwncHFeA/Reca5W6ToXXyCMTeFPdSXA==",
"hasInstallScript": true,
"license": "Apache-2.0",
+ "peer": true,
"dependencies": {
"@prisma/config": "6.16.2",
"@prisma/engines": "6.16.2"
@@ -9795,14 +9861,14 @@
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz",
"integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==",
- "license": "MIT",
- "peer": true
+ "license": "MIT"
},
"node_modules/rollup": {
"version": "4.60.1",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.1.tgz",
"integrity": "sha512-VmtB2rFU/GroZ4oL8+ZqXgSA38O6GR8KSIvWmEFv63pQ0G6KaBH9s07PO8XTXP4vI+3UJUEypOfjkGfmSBBR0w==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"@types/estree": "1.0.8"
},
@@ -10200,7 +10266,6 @@
"resolved": "https://registry.npmjs.org/speakingurl/-/speakingurl-14.0.1.tgz",
"integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==",
"license": "BSD-3-Clause",
- "peer": true,
"engines": {
"node": ">=0.10.0"
}
@@ -10442,7 +10507,6 @@
"resolved": "https://registry.npmjs.org/superjson/-/superjson-2.2.6.tgz",
"integrity": "sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA==",
"license": "MIT",
- "peer": true,
"dependencies": {
"copy-anything": "^4"
},
@@ -10603,6 +10667,7 @@
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz",
"integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"@alloc/quick-lru": "^5.2.0",
"arg": "^5.0.2",
@@ -11411,6 +11476,7 @@
"resolved": "https://registry.npmjs.org/vite/-/vite-7.3.2.tgz",
"integrity": "sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"esbuild": "^0.27.0",
"fdir": "^6.5.0",
@@ -11775,6 +11841,7 @@
"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.32.tgz",
"integrity": "sha512-vM4z4Q9tTafVfMAK7IVzmxg34rSzTFMyIe0UUEijUCkn9+23lj0WRfA83dg7eQZIUlgOSGrkViIaCfqSAUXsMw==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"@vue/compiler-dom": "3.5.32",
"@vue/compiler-sfc": "3.5.32",
@@ -12042,6 +12109,7 @@
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz",
"integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==",
"license": "ISC",
+ "peer": true,
"bin": {
"yaml": "bin.mjs"
},
diff --git a/package.json b/package.json
index 13922ed..dba5985 100644
--- a/package.json
+++ b/package.json
@@ -5,6 +5,8 @@
"scripts": {
"build": "nuxt build",
"dev": "nuxt dev",
+ "format": "prettier . --write",
+ "format:check": "prettier . --check",
"generate": "nuxt generate",
"preview": "nuxt preview",
"postinstall": "nuxt prepare",
@@ -28,6 +30,8 @@
"vue-router": "^5.0.4"
},
"devDependencies": {
- "@types/node": "^25.6.0"
+ "@types/node": "^25.6.0",
+ "prettier": "^3.8.3",
+ "prettier-plugin-tailwindcss": "^0.8.0"
}
}
diff --git a/public/openapi.json b/public/openapi.json
index 50a9118..f3d6130 100644
--- a/public/openapi.json
+++ b/public/openapi.json
@@ -63,12 +63,7 @@
"example": "2026-04-28T12:00:00.000Z"
}
},
- "required": [
- "id",
- "email",
- "created_at",
- "updated_at"
- ]
+ "required": ["id", "email", "created_at", "updated_at"]
},
"TokenResponse": {
"type": "object",
@@ -93,12 +88,7 @@
"example": 900
}
},
- "required": [
- "access_token",
- "refresh_token",
- "token_type",
- "expires_in"
- ]
+ "required": ["access_token", "refresh_token", "token_type", "expires_in"]
},
"ErrorResponse": {
"type": "object",
@@ -112,19 +102,14 @@
"example": "Email é obrigatório"
}
},
- "required": [
- "statusCode",
- "message"
- ]
+ "required": ["statusCode", "message"]
}
}
},
"paths": {
"/api/auth/register": {
"post": {
- "tags": [
- "Auth"
- ],
+ "tags": ["Auth"],
"summary": "Registrar novo usuário",
"description": "Cria uma conta nova. A senha é armazenada como hash scrypt — nunca em plaintext.",
"operationId": "authRegister",
@@ -134,10 +119,7 @@
"application/json": {
"schema": {
"type": "object",
- "required": [
- "email",
- "password"
- ],
+ "required": ["email", "password"],
"properties": {
"email": {
"type": "string",
@@ -200,9 +182,7 @@
},
"/api/auth/login": {
"post": {
- "tags": [
- "Auth"
- ],
+ "tags": ["Auth"],
"summary": "Login",
"description": "Autentica o usuário e retorna um par de tokens (access + refresh).",
"operationId": "authLogin",
@@ -212,10 +192,7 @@
"application/json": {
"schema": {
"type": "object",
- "required": [
- "email",
- "password"
- ],
+ "required": ["email", "password"],
"properties": {
"email": {
"type": "string",
@@ -271,9 +248,7 @@
},
"/api/auth/refresh": {
"post": {
- "tags": [
- "Auth"
- ],
+ "tags": ["Auth"],
"summary": "Renovar tokens",
"description": "Troca um refresh token válido por um novo par de tokens. O token antigo é revogado imediatamente (rotação).",
"operationId": "authRefresh",
@@ -283,9 +258,7 @@
"application/json": {
"schema": {
"type": "object",
- "required": [
- "refresh_token"
- ],
+ "required": ["refresh_token"],
"properties": {
"refresh_token": {
"type": "string",
@@ -337,9 +310,7 @@
},
"/api/auth/forgot-password": {
"post": {
- "tags": [
- "Auth"
- ],
+ "tags": ["Auth"],
"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.",
"operationId": "authForgotPassword",
@@ -349,9 +320,7 @@
"application/json": {
"schema": {
"type": "object",
- "required": [
- "email"
- ],
+ "required": ["email"],
"properties": {
"email": {
"type": "string",
@@ -414,9 +383,7 @@
},
"/api/auth/reset-password": {
"post": {
- "tags": [
- "Auth"
- ],
+ "tags": ["Auth"],
"summary": "Redefinir senha",
"description": "Aplica a nova senha usando o token de reset. O token é de uso único — após utilizado, é marcado como consumido.",
"operationId": "authResetPassword",
@@ -426,10 +393,7 @@
"application/json": {
"schema": {
"type": "object",
- "required": [
- "token",
- "new_password"
- ],
+ "required": ["token", "new_password"],
"properties": {
"token": {
"type": "string",
@@ -493,9 +457,7 @@
},
"/profile/me": {
"get": {
- "tags": [
- "Profile"
- ],
+ "tags": ["Profile"],
"summary": "Perfil do usuário autenticado",
"description": "Retorna os dados do usuário identificado pelo access token JWT.",
"operationId": "profileMe",
@@ -544,9 +506,7 @@
},
"/dashboard": {
"get": {
- "tags": [
- "Dashboard"
- ],
+ "tags": ["Dashboard"],
"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.",
"operationId": "dashboard",
@@ -599,4 +559,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/server/utils/auth-config.ts b/server/utils/auth-config.ts
index d0b258a..3741415 100644
--- a/server/utils/auth-config.ts
+++ b/server/utils/auth-config.ts
@@ -92,7 +92,10 @@ export function getAuthRuntimeConfig(event?: H3Event): AuthRuntimeConfig {
kid,
accessTtlSec: parsePositiveInt(String(runtimeConfig.jwtAccessTtlSec), 'JWT access 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'),
publicKeyPem: normalizePem(String(runtimeConfig.jwtPublicKeyPem ?? ''), 'JWT public key'),
refreshTokenPepper,
diff --git a/server/utils/auth-rules.ts b/server/utils/auth-rules.ts
index 11568c7..cffffcf 100644
--- a/server/utils/auth-rules.ts
+++ b/server/utils/auth-rules.ts
@@ -37,6 +37,8 @@ export function getRouteRequirement(method: string, path: string): AuthRouteRequ
const normalizedPath = normalizePath(path)
return (
- PROTECTED_ROUTES.find((route) => route.method === normalizedMethod && route.path === normalizedPath) ?? null
+ PROTECTED_ROUTES.find(
+ (route) => route.method === normalizedMethod && route.path === normalizedPath
+ ) ?? null
)
}
diff --git a/server/utils/jwt.ts b/server/utils/jwt.ts
index e85b216..4d46ce9 100644
--- a/server/utils/jwt.ts
+++ b/server/utils/jwt.ts
@@ -1,11 +1,5 @@
import { createError, type H3Event } from 'h3'
-import {
- SignJWT,
- importPKCS8,
- importSPKI,
- jwtVerify,
- type JWTPayload
-} from 'jose'
+import { SignJWT, importPKCS8, importSPKI, jwtVerify, type JWTPayload } from 'jose'
import type { AccessTokenClaims } from '../types/auth'
import { getAuthRuntimeConfig } from './auth-config'
@@ -74,7 +68,10 @@ async function loadSigningKeys(event: H3Event): Promise {
* @param payload Dados mínimos do token (apenas `sub`).
* @returns JWT assinado em formato string.
*/
-export async function signAccessToken(event: H3Event, payload: Pick): Promise {
+export async function signAccessToken(
+ event: H3Event,
+ payload: Pick
+): Promise {
const config = getAuthRuntimeConfig(event)
const keys = await loadSigningKeys(event)
diff --git a/server/utils/refresh-token.ts b/server/utils/refresh-token.ts
index 848a1cc..67cd9a6 100644
--- a/server/utils/refresh-token.ts
+++ b/server/utils/refresh-token.ts
@@ -39,7 +39,10 @@ function generateRawRefreshToken(): string {
* @param userId ID do usuário dono do token.
* @returns Metadados do refresh token emitido.
*/
-export async function issueRefreshToken(event: H3Event, userId: string): Promise {
+export async function issueRefreshToken(
+ event: H3Event,
+ userId: string
+): Promise {
const config = getAuthRuntimeConfig(event)
const token = generateRawRefreshToken()
const tokenHash = hashRefreshToken(token, config.refreshTokenPepper)
diff --git a/tsconfig.json b/tsconfig.json
index 28b66c5..a746f2a 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,4 +1,4 @@
{
// https://nuxt.com/docs/guide/concepts/typescript
"extends": "./.nuxt/tsconfig.json"
-}
\ No newline at end of file
+}