devsquad 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,187 @@
1
+ # NestJS — Autenticação JWT
2
+
3
+ ## Prisma Schema (auth)
4
+
5
+ ```prisma
6
+ model User {
7
+ id String @id @default(uuid())
8
+ email String @unique
9
+ password String
10
+ name String
11
+ role Role @default(USER)
12
+ hashedRefreshToken String? // para refresh token rotation
13
+ createdAt DateTime @default(now())
14
+ updatedAt DateTime @updatedAt
15
+ deletedAt DateTime?
16
+
17
+ @@map("users")
18
+ @@index([email])
19
+ }
20
+
21
+ enum Role { ADMIN USER }
22
+ ```
23
+
24
+ > 📖 UUID evita enumeração de IDs — OWASP A01. `hashedRefreshToken` armazenado como hash (bcrypt) para invalidação segura.
25
+
26
+ ## JWT Strategy
27
+
28
+ ```typescript
29
+ // strategies/jwt.strategy.ts
30
+ import { Injectable, UnauthorizedException } from '@nestjs/common';
31
+ import { PassportStrategy } from '@nestjs/passport';
32
+ import { ExtractJwt, Strategy } from 'passport-jwt';
33
+ import { ConfigService } from '@nestjs/config';
34
+ import { PrismaService } from '../../prisma/prisma.service';
35
+
36
+ @Injectable()
37
+ export class JwtStrategy extends PassportStrategy(Strategy) {
38
+ constructor(config: ConfigService, private prisma: PrismaService) {
39
+ super({
40
+ jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
41
+ secretOrKey: config.get('jwt.secret'),
42
+ });
43
+ }
44
+
45
+ async validate(payload: { sub: string; email: string; role: string }) {
46
+ const user = await this.prisma.user.findUnique({
47
+ where: { id: payload.sub, deletedAt: null },
48
+ });
49
+ if (!user) throw new UnauthorizedException();
50
+ const { password, hashedRefreshToken, ...result } = user;
51
+ return result;
52
+ }
53
+ }
54
+ ```
55
+
56
+ ## Auth Service — Login + Refresh Token Rotation
57
+
58
+ ```typescript
59
+ // auth.service.ts
60
+ import { Injectable, UnauthorizedException, ForbiddenException } from '@nestjs/common';
61
+ import { JwtService } from '@nestjs/jwt';
62
+ import { ConfigService } from '@nestjs/config';
63
+ import { PrismaService } from '../../prisma/prisma.service';
64
+ import * as bcrypt from 'bcrypt';
65
+
66
+ @Injectable()
67
+ export class AuthService {
68
+ constructor(
69
+ private prisma: PrismaService,
70
+ private jwt: JwtService,
71
+ private config: ConfigService,
72
+ ) {}
73
+
74
+ async login(email: string, password: string) {
75
+ const user = await this.prisma.user.findUnique({ where: { email } });
76
+
77
+ // 🔒 Mesma mensagem para email e senha inválidos — evita user enumeration
78
+ if (!user || !(await bcrypt.compare(password, user.password))) {
79
+ throw new UnauthorizedException('Credenciais inválidas');
80
+ }
81
+
82
+ const tokens = await this.generateTokens(user.id, user.email, user.role);
83
+ await this.updateRefreshToken(user.id, tokens.refresh_token);
84
+ return tokens;
85
+ }
86
+
87
+ async refresh(userId: string, refreshToken: string) {
88
+ const user = await this.prisma.user.findUnique({ where: { id: userId } });
89
+ if (!user?.hashedRefreshToken) throw new ForbiddenException();
90
+
91
+ const isValid = await bcrypt.compare(refreshToken, user.hashedRefreshToken);
92
+ if (!isValid) throw new ForbiddenException();
93
+
94
+ // 🔒 Rotation — invalida o token anterior a cada uso
95
+ const tokens = await this.generateTokens(user.id, user.email, user.role);
96
+ await this.updateRefreshToken(user.id, tokens.refresh_token);
97
+ return tokens;
98
+ }
99
+
100
+ async logout(userId: string) {
101
+ await this.prisma.user.update({
102
+ where: { id: userId },
103
+ data: { hashedRefreshToken: null },
104
+ });
105
+ }
106
+
107
+ private async generateTokens(userId: string, email: string, role: string) {
108
+ const payload = { sub: userId, email, role };
109
+ const [access_token, refresh_token] = await Promise.all([
110
+ this.jwt.signAsync(payload, {
111
+ secret: this.config.get('jwt.secret'),
112
+ expiresIn: '15m',
113
+ }),
114
+ this.jwt.signAsync(payload, {
115
+ secret: this.config.get('jwt.refreshSecret'),
116
+ expiresIn: '7d',
117
+ }),
118
+ ]);
119
+ return { access_token, refresh_token };
120
+ }
121
+
122
+ private async updateRefreshToken(userId: string, refreshToken: string) {
123
+ const hashed = await bcrypt.hash(refreshToken, 12);
124
+ await this.prisma.user.update({
125
+ where: { id: userId },
126
+ data: { hashedRefreshToken: hashed },
127
+ });
128
+ }
129
+ }
130
+ ```
131
+
132
+ ## Guards e RBAC
133
+
134
+ ```typescript
135
+ // guards/jwt-auth.guard.ts
136
+ import { Injectable } from '@nestjs/common';
137
+ import { AuthGuard } from '@nestjs/passport';
138
+ @Injectable()
139
+ export class JwtAuthGuard extends AuthGuard('jwt') {}
140
+
141
+ // decorators/roles.decorator.ts
142
+ import { SetMetadata } from '@nestjs/common';
143
+ export const Roles = (...roles: string[]) => SetMetadata('roles', roles);
144
+
145
+ // guards/roles.guard.ts
146
+ import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
147
+ import { Reflector } from '@nestjs/core';
148
+ @Injectable()
149
+ export class RolesGuard implements CanActivate {
150
+ constructor(private reflector: Reflector) {}
151
+ canActivate(ctx: ExecutionContext): boolean {
152
+ const roles = this.reflector.get<string[]>('roles', ctx.getHandler());
153
+ if (!roles) return true;
154
+ return roles.includes(ctx.switchToHttp().getRequest().user?.role);
155
+ }
156
+ }
157
+
158
+ // decorators/current-user.decorator.ts
159
+ import { createParamDecorator, ExecutionContext } from '@nestjs/common';
160
+ export const CurrentUser = createParamDecorator(
161
+ (_: unknown, ctx: ExecutionContext) => ctx.switchToHttp().getRequest().user,
162
+ );
163
+ ```
164
+
165
+ ## Uso no Controller
166
+
167
+ ```typescript
168
+ @Get('profile')
169
+ @UseGuards(JwtAuthGuard)
170
+ getProfile(@CurrentUser() user: User) { return user; }
171
+
172
+ @Get('admin')
173
+ @UseGuards(JwtAuthGuard, RolesGuard)
174
+ @Roles('ADMIN')
175
+ adminOnly() { return 'só admin'; }
176
+ ```
177
+
178
+ ## Checklist de Auth
179
+
180
+ - [ ] bcrypt com salt 12 no hash de senhas
181
+ - [ ] Mesma mensagem de erro para email/senha inválidos
182
+ - [ ] Access token 15min + Refresh token 7d
183
+ - [ ] Refresh token salvo como hash (bcrypt) no banco
184
+ - [ ] Rotation implementada — token anterior invalidado a cada `/refresh`
185
+ - [ ] Rate limiting no `/auth/login` — `@Throttle({ default: { ttl: 900000, limit: 5 } })`
186
+ - [ ] `JwtAuthGuard` e `RolesGuard` criados e testados
187
+ - [ ] `CurrentUser` decorator funcionando
@@ -0,0 +1,110 @@
1
+ # NestJS — Padrão de módulo (DDD leve)
2
+
3
+ Use este padrão para cada feature em `src/modules/<nome>/`.
4
+
5
+ ## Estrutura de pastas
6
+
7
+ ```
8
+ src/modules/users/
9
+ ├── users.module.ts
10
+ ├── users.controller.ts
11
+ ├── users.service.ts
12
+ ├── dto/
13
+ │ ├── create-user.dto.ts
14
+ │ └── update-user.dto.ts
15
+ └── entities/
16
+ └── user.entity.ts ← opcional: classe pura ou type do Prisma
17
+ ```
18
+
19
+ ## users.module.ts
20
+
21
+ ```typescript
22
+ import { Module } from '@nestjs/common';
23
+ import { UsersService } from './users.service';
24
+ import { UsersController } from './users.controller';
25
+ import { PrismaModule } from '../../prisma/prisma.module';
26
+
27
+ @Module({
28
+ imports: [PrismaModule],
29
+ controllers: [UsersController],
30
+ providers: [UsersService],
31
+ exports: [UsersService],
32
+ })
33
+ export class UsersModule {}
34
+ ```
35
+
36
+ ## DTOs (A03 — validação)
37
+
38
+ ```typescript
39
+ // create-user.dto.ts
40
+ import { IsEmail, IsString, MinLength, MaxLength } from 'class-validator';
41
+
42
+ export class CreateUserDto {
43
+ @IsEmail()
44
+ email: string;
45
+
46
+ @IsString()
47
+ @MinLength(8)
48
+ @MaxLength(128)
49
+ password: string;
50
+
51
+ @IsString()
52
+ @MaxLength(100)
53
+ name: string;
54
+ }
55
+ ```
56
+
57
+ ## Service (Prisma)
58
+
59
+ ```typescript
60
+ import { Injectable, NotFoundException } from '@nestjs/common';
61
+ import { PrismaService } from '../../prisma/prisma.service';
62
+ import { CreateUserDto } from './dto/create-user.dto';
63
+
64
+ @Injectable()
65
+ export class UsersService {
66
+ constructor(private prisma: PrismaService) {}
67
+
68
+ async create(dto: CreateUserDto) {
69
+ // hash de senha no service ou em subcamada dedicada — ver auth.md
70
+ return this.prisma.user.create({ data: { /* mapear dto */ } });
71
+ }
72
+
73
+ async findOne(id: string) {
74
+ const user = await this.prisma.user.findUnique({ where: { id } });
75
+ if (!user) throw new NotFoundException();
76
+ return user;
77
+ }
78
+ }
79
+ ```
80
+
81
+ ## Controller
82
+
83
+ ```typescript
84
+ import { Controller, Get, Post, Body, Param, UseGuards } from '@nestjs/common';
85
+ import { UsersService } from './users.service';
86
+ import { CreateUserDto } from './dto/create-user.dto';
87
+ import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
88
+
89
+ @Controller('users')
90
+ export class UsersController {
91
+ constructor(private readonly usersService: UsersService) {}
92
+
93
+ @Post()
94
+ create(@Body() dto: CreateUserDto) {
95
+ return this.usersService.create(dto);
96
+ }
97
+
98
+ @Get(':id')
99
+ @UseGuards(JwtAuthGuard)
100
+ findOne(@Param('id') id: string) {
101
+ return this.usersService.findOne(id);
102
+ }
103
+ }
104
+ ```
105
+
106
+ ## Regras
107
+
108
+ - **Um módulo = uma bounded context** clara (users, orders, billing).
109
+ - **Nunca** exponha entidades Prisma brutas se houver campos sensíveis — use resposta DTO ou `select`.
110
+ - Rotas protegidas: `@UseGuards(JwtAuthGuard)` + ownership quando o recurso for do usuário (ver skill **security**).
@@ -0,0 +1,42 @@
1
+ # NestJS — Segurança (OWASP no backend)
2
+
3
+ Checklist e pontos **específicos de NestJS**. Para o checklist completo e exemplos genéricos, use a skill **`/devsquad security`** (`security/SKILL.md`).
4
+
5
+ ## Já coberto em setup.md
6
+
7
+ - `helmet()`, `ValidationPipe` com `whitelist` + `forbidNonWhitelisted`
8
+ - CORS explícito, `ThrottlerGuard` global
9
+ - Swagger em `/api/docs` com Bearer
10
+
11
+ ## Antes de ir para produção
12
+
13
+ | Item | Ação |
14
+ |------|------|
15
+ | **A02** | `JWT_SECRET` / `JWT_REFRESH_SECRET` fortes, nunca no código |
16
+ | **A04** | Login: throttle mais agressivo (ex.: guard dedicado na rota `auth/login`) |
17
+ | **A05** | HTTPS atrás de proxy; confiar em `X-Forwarded-*` só se configurado |
18
+ | **A07** | Access token curto + refresh com rotation (ver `auth.md`) |
19
+ | **A09** | Logger sem email, senha, token ou body completo de erro para cliente |
20
+
21
+ ## Snippets úteis
22
+
23
+ ### Rate limit na rota de login
24
+
25
+ ```typescript
26
+ import { Throttle } from '@nestjs/throttler';
27
+
28
+ @Throttle({ default: { limit: 5, ttl: 900000 } }) // 5 / 15 min
29
+ @Post('login')
30
+ login(@Body() dto: LoginDto) { /* ... */ }
31
+ ```
32
+
33
+ ### Não vazar stack trace
34
+
35
+ ```typescript
36
+ // Em produção: filtre detalhes em ExceptionFilter global
37
+ ```
38
+
39
+ ## Referência cruzada
40
+
41
+ - **auth.md** — JWT, guards, refresh
42
+ - **security (skill)** — OWASP Top 10 completo para stack Nest + React
@@ -0,0 +1,162 @@
1
+ # NestJS — Setup Completo
2
+
3
+ ## src/main.ts
4
+
5
+ ```typescript
6
+ import { NestFactory } from '@nestjs/core';
7
+ import { ValidationPipe, Logger } from '@nestjs/common';
8
+ import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
9
+ import { AppModule } from './app.module';
10
+ import helmet from 'helmet';
11
+
12
+ async function bootstrap() {
13
+ const app = await NestFactory.create(AppModule);
14
+ const logger = new Logger('Bootstrap');
15
+
16
+ // 🔒 OWASP A05 — Headers de segurança HTTP
17
+ app.use(helmet());
18
+
19
+ // 🔒 OWASP A03 — Rejeita campos não declarados nos DTOs
20
+ app.useGlobalPipes(new ValidationPipe({
21
+ whitelist: true,
22
+ forbidNonWhitelisted: true,
23
+ transform: true,
24
+ }));
25
+
26
+ // CORS explícito — nunca use '*' em produção
27
+ app.enableCors({ origin: process.env.FRONTEND_URL, credentials: true });
28
+
29
+ app.setGlobalPrefix('api/v1');
30
+
31
+ // Swagger
32
+ const config = new DocumentBuilder()
33
+ .setTitle(process.env.APP_NAME ?? 'API')
34
+ .setVersion('1.0')
35
+ .addBearerAuth()
36
+ .build();
37
+ SwaggerModule.setup('api/docs', app, SwaggerModule.createDocument(app, config));
38
+
39
+ const port = process.env.PORT ?? 3000;
40
+ await app.listen(port);
41
+ logger.log(`🚀 http://localhost:${port}`);
42
+ logger.log(`📚 Swagger: http://localhost:${port}/api/docs`);
43
+ }
44
+ bootstrap();
45
+ ```
46
+
47
+ ## src/app.module.ts
48
+
49
+ ```typescript
50
+ import { Module } from '@nestjs/common';
51
+ import { ConfigModule } from '@nestjs/config';
52
+ import { ThrottlerModule, ThrottlerGuard } from '@nestjs/throttler';
53
+ import { APP_GUARD } from '@nestjs/core';
54
+ import { PrismaModule } from './prisma/prisma.module';
55
+ import { AuthModule } from './modules/auth/auth.module';
56
+ import { UsersModule } from './modules/users/users.module';
57
+ import configuration from './config/configuration';
58
+
59
+ @Module({
60
+ imports: [
61
+ ConfigModule.forRoot({ isGlobal: true, load: [configuration] }),
62
+ ThrottlerModule.forRoot([{ ttl: 60000, limit: 60 }]),
63
+ PrismaModule,
64
+ AuthModule,
65
+ UsersModule,
66
+ ],
67
+ providers: [{ provide: APP_GUARD, useClass: ThrottlerGuard }],
68
+ })
69
+ export class AppModule {}
70
+ ```
71
+
72
+ ## src/config/configuration.ts
73
+
74
+ ```typescript
75
+ export default () => ({
76
+ port: parseInt(process.env.PORT ?? '3000', 10),
77
+ jwt: {
78
+ secret: process.env.JWT_SECRET,
79
+ refreshSecret: process.env.JWT_REFRESH_SECRET,
80
+ expiresIn: '15m',
81
+ refreshExpiresIn: '7d',
82
+ },
83
+ frontendUrl: process.env.FRONTEND_URL,
84
+ });
85
+ ```
86
+
87
+ ## src/prisma/prisma.service.ts
88
+
89
+ ```typescript
90
+ import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common';
91
+ import { PrismaClient } from '@prisma/client';
92
+
93
+ @Injectable()
94
+ export class PrismaService extends PrismaClient
95
+ implements OnModuleInit, OnModuleDestroy {
96
+ async onModuleInit() { await this.$connect(); }
97
+ async onModuleDestroy() { await this.$disconnect(); }
98
+ }
99
+ ```
100
+
101
+ ```typescript
102
+ // prisma/prisma.module.ts
103
+ import { Global, Module } from '@nestjs/common';
104
+ import { PrismaService } from './prisma.service';
105
+
106
+ @Global()
107
+ @Module({ providers: [PrismaService], exports: [PrismaService] })
108
+ export class PrismaModule {}
109
+ ```
110
+
111
+ ## .env.example
112
+
113
+ ```env
114
+ APP_NAME="Nome do Projeto"
115
+ PORT=3000
116
+ NODE_ENV=development
117
+ FRONTEND_URL="http://localhost:5173"
118
+ DATABASE_URL="postgresql://postgres:postgres@localhost:5432/nome_db?schema=public"
119
+ JWT_SECRET="gere-com-openssl-rand-base64-32"
120
+ JWT_REFRESH_SECRET="outro-segredo-diferente"
121
+ ```
122
+
123
+ ## Estrutura de pastas (DDD)
124
+
125
+ ```
126
+ src/
127
+ ├── modules/
128
+ │ ├── auth/
129
+ │ │ ├── dto/ login.dto.ts, register.dto.ts
130
+ │ │ ├── guards/ jwt-auth.guard.ts
131
+ │ │ ├── strategies/ jwt.strategy.ts
132
+ │ │ ├── decorators/ current-user.decorator.ts
133
+ │ │ ├── auth.controller.ts
134
+ │ │ ├── auth.module.ts
135
+ │ │ └── auth.service.ts
136
+ │ └── users/
137
+ │ ├── dto/ create-user.dto.ts, update-user.dto.ts
138
+ │ ├── entities/ user.entity.ts
139
+ │ ├── users.controller.ts
140
+ │ ├── users.module.ts
141
+ │ └── users.service.ts
142
+ ├── common/
143
+ │ ├── decorators/ roles.decorator.ts
144
+ │ ├── guards/ roles.guard.ts
145
+ │ └── filters/ http-exception.filter.ts
146
+ ├── config/
147
+ │ └── configuration.ts
148
+ ├── prisma/
149
+ │ ├── prisma.module.ts
150
+ │ └── prisma.service.ts
151
+ └── main.ts
152
+ ```
153
+
154
+ ## Checklist de setup
155
+
156
+ - [ ] NestJS inicializado via CLI
157
+ - [ ] `helmet` + `ValidationPipe` global + CORS configurados no `main.ts`
158
+ - [ ] `PrismaModule` com `@Global()` criado
159
+ - [ ] `ThrottlerModule` configurado no `AppModule`
160
+ - [ ] `.env.example` criado e `.env` no `.gitignore`
161
+ - [ ] Swagger funcionando em `/api/docs`
162
+ - [ ] Prefixo global `api/v1` definido
@@ -0,0 +1,82 @@
1
+ ---
2
+ name: postman-collections
3
+ description: Criação e organização de Postman Collections profissionais para documentar e testar APIs. Use quando o desenvolvedor precisar documentar endpoints, criar environments, escrever scripts de teste ou exportar a collection para o repositório.
4
+ ---
5
+
6
+ # Postman Collections
7
+
8
+ ## Estrutura da collection
9
+
10
+ ```
11
+ 📁 [Nome do Projeto] API
12
+ ├── 📁 Auth
13
+ │ ├── POST /auth/register
14
+ │ ├── POST /auth/login ← script de captura automática de token
15
+ │ ├── POST /auth/refresh
16
+ │ └── POST /auth/logout
17
+ └── 📁 [Módulo por módulo]
18
+ ```
19
+
20
+ ## Environments (nunca hardcode URLs ou tokens)
21
+
22
+ **Development**
23
+ ```json
24
+ { "base_url": "http://localhost:3000/api/v1", "access_token": "", "refresh_token": "" }
25
+ ```
26
+
27
+ **Production**
28
+ ```json
29
+ { "base_url": "https://api.seudominio.com/api/v1", "access_token": "", "refresh_token": "" }
30
+ ```
31
+
32
+ Uso nas requisições: `{{base_url}}/auth/login` | `Bearer {{access_token}}`
33
+
34
+ ## Script de captura automática de token (aba Tests do login)
35
+
36
+ ```javascript
37
+ const { access_token, refresh_token } = pm.response.json();
38
+ if (access_token) {
39
+ pm.environment.set('access_token', access_token);
40
+ pm.environment.set('refresh_token', refresh_token);
41
+ }
42
+ pm.test('Status 200', () => pm.response.to.have.status(200));
43
+ pm.test('Retorna tokens', () => {
44
+ pm.expect(pm.response.json()).to.have.property('access_token');
45
+ });
46
+ ```
47
+
48
+ ## Testes padrão por endpoint
49
+
50
+ ```javascript
51
+ pm.test('Status correto', () => pm.response.to.have.status(201));
52
+ pm.test('Estrutura correta', () => {
53
+ const json = pm.response.json();
54
+ pm.expect(json).to.have.property('id');
55
+ pm.expect(json).not.to.have.property('password'); // 🔒
56
+ });
57
+ pm.test('Resposta < 500ms', () => pm.expect(pm.response.responseTime).to.be.below(500));
58
+ ```
59
+
60
+ ## Versionamento no repositório
61
+
62
+ ```
63
+ postman/
64
+ ├── collection.json ← Exportar: ··· > Export > Collection v2.1
65
+ ├── environment.dev.json ← Sem valores reais
66
+ └── environment.prod.json ← Sem valores reais
67
+ ```
68
+
69
+ ```bash
70
+ git commit -m "docs(postman): adicionar endpoints do módulo de auth"
71
+ ```
72
+
73
+ > 🔒 Nunca commite tokens reais nos arquivos de environment.
74
+
75
+ ## Checklist
76
+
77
+ - [ ] Collection com o nome do projeto
78
+ - [ ] Environments dev e prod configurados com variáveis
79
+ - [ ] Todas as URLs usando `{{base_url}}`
80
+ - [ ] Script de auto-captura de token no login
81
+ - [ ] Testes básicos (status + estrutura) em cada endpoint
82
+ - [ ] Collection exportada em `postman/collection.json`
@@ -0,0 +1,53 @@
1
+ ---
2
+ name: react-frontend
3
+ description: Setup e arquitetura de frontend com React + TypeScript + TailwindCSS + TanStack Query. Use quando o desenvolvedor precisar inicializar o frontend web, configurar autenticação no cliente, criar componentes, hooks, services ou rotas protegidas.
4
+ ---
5
+
6
+ # React Frontend
7
+
8
+ Stack: React + TypeScript + Vite + TailwindCSS + TanStack Query + Axios + React Router + React Hook Form + Zod
9
+
10
+ ## Setup rápido
11
+
12
+ ```bash
13
+ npm create vite@latest nome-do-projeto -- --template react-ts
14
+ cd nome-do-projeto
15
+ npm install -D tailwindcss postcss autoprefixer && npx tailwindcss init -p
16
+ npm install axios @tanstack/react-query react-router-dom react-hook-form zod @hookform/resolvers lucide-react
17
+ ```
18
+
19
+ ```javascript
20
+ // tailwind.config.js
21
+ export default {
22
+ content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
23
+ theme: { extend: {} },
24
+ plugins: [],
25
+ }
26
+ ```
27
+
28
+ ## Estrutura de pastas
29
+
30
+ ```
31
+ src/
32
+ ├── components/ui/ ← Button, Input, Card reutilizáveis
33
+ ├── components/layout/ ← Header, Sidebar, PrivateLayout
34
+ ├── pages/ ← Uma pasta por rota
35
+ ├── hooks/ ← Custom hooks
36
+ ├── services/ ← api.ts + *.service.ts (nunca axios direto no componente)
37
+ ├── contexts/ ← AuthContext.tsx
38
+ ├── routes/ ← AppRoutes.tsx + PrivateRoute.tsx
39
+ ├── types/ ← interfaces TypeScript
40
+ └── utils/ ← funções puras
41
+ ```
42
+
43
+ ## .env.example
44
+
45
+ ```env
46
+ VITE_API_URL=http://localhost:3000/api/v1
47
+ ```
48
+
49
+ ## Arquivos detalhados desta skill
50
+
51
+ - **setup.md** — AuthContext, Axios com interceptors, PrivateRoute, App.tsx prontos
52
+
53
+ Leia `setup.md` para implementar a estrutura base completa.