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 @@ \ 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 @@ \ 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 +}