create-tigra 1.0.0 → 1.0.2
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.
- package/bin/create-tigra.js +7 -3
- package/package.json +1 -1
- package/template/server/.env.example +69 -148
- package/template/server/README.md +25 -17
- package/template/server/package.json +5 -7
- package/template/server/prisma/schema.prisma +2 -1
- package/template/server/scripts/setup-env.js +50 -0
- package/template/server/scripts/wait-for-db.js +3 -3
- package/template/server/src/app.ts +0 -3
- package/template/server/src/config/env.ts +0 -7
- package/template/server/src/modules/admin/admin.routes.ts +0 -38
- package/template/server/src/modules/auth/auth.routes.ts +8 -129
- package/template/server/src/modules/resources/resources.routes.ts +10 -249
- package/template/.agent/rules/server/09-api-documentation-v2.md +0 -168
- package/template/.claude/rules/server-09-api-documentation-v2.md +0 -169
- package/template/server/src/libs/swagger-schemas.ts +0 -16
- package/template/server/src/plugins/swagger.plugin.ts +0 -41
- package/template/server/tsconfig.build.tsbuildinfo +0 -1
- /package/template/server/{postman_collection.json → Tigra-API.postman_collection.json} +0 -0
|
@@ -1,25 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Authentication Routes
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
4
|
* Fastify route definitions for authentication endpoints.
|
|
5
|
-
* Includes
|
|
6
|
-
*
|
|
7
|
-
* @see /mnt/project/09-api-documentation-v2.md
|
|
5
|
+
* Includes rate limiting for security.
|
|
6
|
+
*
|
|
8
7
|
* @see /mnt/project/11-rate-limiting-v2.md
|
|
9
8
|
*/
|
|
10
9
|
|
|
11
10
|
import type { FastifyInstance } from 'fastify';
|
|
12
|
-
import { toJsonSchema, getDefinition } from '@/libs/swagger-schemas';
|
|
13
11
|
import * as authController from './auth.controller';
|
|
14
|
-
import {
|
|
15
|
-
RegisterSchema,
|
|
16
|
-
LoginSchema,
|
|
17
|
-
RefreshTokenSchema,
|
|
18
|
-
AuthResponseSchema,
|
|
19
|
-
UserResponseSchema,
|
|
20
|
-
TokenResponseSchema,
|
|
21
|
-
ErrorResponseSchema,
|
|
22
|
-
} from './auth.schemas';
|
|
23
12
|
|
|
24
13
|
/**
|
|
25
14
|
* Register authentication routes
|
|
@@ -29,27 +18,10 @@ import {
|
|
|
29
18
|
export async function authRoutes(fastify: FastifyInstance): Promise<void> {
|
|
30
19
|
/**
|
|
31
20
|
* POST /auth/register
|
|
32
|
-
*
|
|
21
|
+
*
|
|
33
22
|
* Register a new user account
|
|
34
23
|
*/
|
|
35
24
|
fastify.post('/register', {
|
|
36
|
-
schema: {
|
|
37
|
-
description: 'Register a new user account',
|
|
38
|
-
tags: ['auth'],
|
|
39
|
-
summary: 'Register user',
|
|
40
|
-
body: toJsonSchema(RegisterSchema, 'RegisterRequest'),
|
|
41
|
-
response: {
|
|
42
|
-
201: getDefinition(AuthResponseSchema, 'AuthResponse'),
|
|
43
|
-
400: {
|
|
44
|
-
description: 'Validation error',
|
|
45
|
-
...getDefinition(ErrorResponseSchema, 'ErrorResponse'),
|
|
46
|
-
},
|
|
47
|
-
409: {
|
|
48
|
-
description: 'Email already registered',
|
|
49
|
-
...getDefinition(ErrorResponseSchema, 'ErrorResponse'),
|
|
50
|
-
},
|
|
51
|
-
},
|
|
52
|
-
},
|
|
53
25
|
config: {
|
|
54
26
|
rateLimit: {
|
|
55
27
|
max: 3,
|
|
@@ -61,23 +33,10 @@ export async function authRoutes(fastify: FastifyInstance): Promise<void> {
|
|
|
61
33
|
|
|
62
34
|
/**
|
|
63
35
|
* POST /auth/login
|
|
64
|
-
*
|
|
36
|
+
*
|
|
65
37
|
* Login with email and password
|
|
66
38
|
*/
|
|
67
39
|
fastify.post('/login', {
|
|
68
|
-
schema: {
|
|
69
|
-
description: 'Login with email and password',
|
|
70
|
-
tags: ['auth'],
|
|
71
|
-
summary: 'Login user',
|
|
72
|
-
body: toJsonSchema(LoginSchema, 'LoginRequest'),
|
|
73
|
-
response: {
|
|
74
|
-
200: getDefinition(AuthResponseSchema, 'AuthResponse'),
|
|
75
|
-
401: {
|
|
76
|
-
description: 'Invalid credentials',
|
|
77
|
-
...getDefinition(ErrorResponseSchema, 'ErrorResponse'),
|
|
78
|
-
},
|
|
79
|
-
},
|
|
80
|
-
},
|
|
81
40
|
config: {
|
|
82
41
|
rateLimit: {
|
|
83
42
|
max: 5,
|
|
@@ -89,37 +48,10 @@ export async function authRoutes(fastify: FastifyInstance): Promise<void> {
|
|
|
89
48
|
|
|
90
49
|
/**
|
|
91
50
|
* POST /auth/refresh
|
|
92
|
-
*
|
|
51
|
+
*
|
|
93
52
|
* Refresh access token using refresh token
|
|
94
53
|
*/
|
|
95
54
|
fastify.post('/refresh', {
|
|
96
|
-
schema: {
|
|
97
|
-
description: 'Refresh access token using refresh token',
|
|
98
|
-
tags: ['auth'],
|
|
99
|
-
summary: 'Refresh tokens',
|
|
100
|
-
body: toJsonSchema(RefreshTokenSchema, 'RefreshTokenRequest'),
|
|
101
|
-
response: {
|
|
102
|
-
200: {
|
|
103
|
-
description: 'Tokens refreshed successfully',
|
|
104
|
-
type: 'object',
|
|
105
|
-
properties: {
|
|
106
|
-
success: { type: 'boolean', enum: [true] },
|
|
107
|
-
message: { type: 'string' },
|
|
108
|
-
data: {
|
|
109
|
-
type: 'object',
|
|
110
|
-
properties: {
|
|
111
|
-
accessToken: { type: 'string' },
|
|
112
|
-
refreshToken: { type: 'string' },
|
|
113
|
-
},
|
|
114
|
-
},
|
|
115
|
-
},
|
|
116
|
-
},
|
|
117
|
-
401: {
|
|
118
|
-
description: 'Invalid or expired refresh token',
|
|
119
|
-
...getDefinition(ErrorResponseSchema, 'ErrorResponse'),
|
|
120
|
-
},
|
|
121
|
-
},
|
|
122
|
-
},
|
|
123
55
|
config: {
|
|
124
56
|
rateLimit: {
|
|
125
57
|
max: 10,
|
|
@@ -131,73 +63,20 @@ export async function authRoutes(fastify: FastifyInstance): Promise<void> {
|
|
|
131
63
|
|
|
132
64
|
/**
|
|
133
65
|
* POST /auth/logout
|
|
134
|
-
*
|
|
66
|
+
*
|
|
135
67
|
* Logout user by invalidating refresh token
|
|
136
68
|
*/
|
|
137
69
|
fastify.post('/logout', {
|
|
138
|
-
schema: {
|
|
139
|
-
description: 'Logout user by invalidating refresh token',
|
|
140
|
-
tags: ['auth'],
|
|
141
|
-
summary: 'Logout user',
|
|
142
|
-
body: toJsonSchema(RefreshTokenSchema, 'RefreshTokenRequest'),
|
|
143
|
-
response: {
|
|
144
|
-
200: {
|
|
145
|
-
description: 'Logout successful',
|
|
146
|
-
type: 'object',
|
|
147
|
-
properties: {
|
|
148
|
-
success: { type: 'boolean', enum: [true] },
|
|
149
|
-
message: { type: 'string' },
|
|
150
|
-
data: { type: 'null' },
|
|
151
|
-
},
|
|
152
|
-
},
|
|
153
|
-
},
|
|
154
|
-
},
|
|
155
70
|
handler: authController.logout,
|
|
156
71
|
});
|
|
157
72
|
|
|
158
73
|
/**
|
|
159
74
|
* GET /auth/me
|
|
160
|
-
*
|
|
75
|
+
*
|
|
161
76
|
* Get current authenticated user information
|
|
162
77
|
* Requires authentication
|
|
163
78
|
*/
|
|
164
79
|
fastify.get('/me', {
|
|
165
|
-
schema: {
|
|
166
|
-
description: 'Get current authenticated user information',
|
|
167
|
-
tags: ['auth'],
|
|
168
|
-
summary: 'Get current user',
|
|
169
|
-
security: [{ bearerAuth: [] }],
|
|
170
|
-
response: {
|
|
171
|
-
200: {
|
|
172
|
-
description: 'User retrieved successfully',
|
|
173
|
-
type: 'object',
|
|
174
|
-
properties: {
|
|
175
|
-
success: { type: 'boolean', enum: [true] },
|
|
176
|
-
message: { type: 'string' },
|
|
177
|
-
data: {
|
|
178
|
-
type: 'object',
|
|
179
|
-
properties: {
|
|
180
|
-
id: { type: 'string', format: 'uuid' },
|
|
181
|
-
email: { type: 'string', format: 'email' },
|
|
182
|
-
name: { type: 'string', nullable: true },
|
|
183
|
-
role: { type: 'string' },
|
|
184
|
-
emailVerified: { type: 'boolean' },
|
|
185
|
-
createdAt: { type: 'string', format: 'date-time' },
|
|
186
|
-
updatedAt: { type: 'string', format: 'date-time' },
|
|
187
|
-
},
|
|
188
|
-
},
|
|
189
|
-
},
|
|
190
|
-
},
|
|
191
|
-
401: {
|
|
192
|
-
description: 'Unauthorized - Invalid or missing token',
|
|
193
|
-
...getDefinition(ErrorResponseSchema, 'ErrorResponse'),
|
|
194
|
-
},
|
|
195
|
-
404: {
|
|
196
|
-
description: 'User not found',
|
|
197
|
-
...getDefinition(ErrorResponseSchema, 'ErrorResponse'),
|
|
198
|
-
},
|
|
199
|
-
},
|
|
200
|
-
},
|
|
201
80
|
preHandler: [fastify.authenticate],
|
|
202
81
|
handler: authController.getMe,
|
|
203
82
|
});
|
|
@@ -1,127 +1,27 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Resources Routes
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
4
|
* Fastify route definitions for resources endpoints.
|
|
5
|
-
* Includes
|
|
6
|
-
*
|
|
7
|
-
* @see /mnt/project/09-api-documentation-v2.md
|
|
5
|
+
* Includes rate limiting for security.
|
|
6
|
+
*
|
|
8
7
|
* @see /mnt/project/11-rate-limiting-v2.md
|
|
9
8
|
*/
|
|
10
9
|
|
|
11
10
|
import type { FastifyInstance } from 'fastify';
|
|
12
|
-
import { toJsonSchema, getDefinition } from '@/libs/swagger-schemas';
|
|
13
11
|
import * as resourceController from './resources.controller';
|
|
14
|
-
import {
|
|
15
|
-
CreateResourceSchema,
|
|
16
|
-
UpdateResourceSchema,
|
|
17
|
-
ResourceFiltersSchema,
|
|
18
|
-
PaginationSchema,
|
|
19
|
-
ResourceResponseSchema,
|
|
20
|
-
ResourceWithOwnerResponseSchema,
|
|
21
|
-
} from './resources.schemas';
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Error response schema for Swagger
|
|
25
|
-
*/
|
|
26
|
-
const ErrorResponseSchema = {
|
|
27
|
-
type: 'object',
|
|
28
|
-
properties: {
|
|
29
|
-
success: { type: 'boolean', enum: [false] },
|
|
30
|
-
error: {
|
|
31
|
-
type: 'object',
|
|
32
|
-
properties: {
|
|
33
|
-
code: { type: 'string' },
|
|
34
|
-
message: { type: 'string' },
|
|
35
|
-
},
|
|
36
|
-
},
|
|
37
|
-
},
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Paginated resources response schema
|
|
42
|
-
*/
|
|
43
|
-
const PaginatedResourcesResponseSchema = {
|
|
44
|
-
type: 'object',
|
|
45
|
-
properties: {
|
|
46
|
-
success: { type: 'boolean', enum: [true] },
|
|
47
|
-
message: { type: 'string' },
|
|
48
|
-
data: {
|
|
49
|
-
type: 'object',
|
|
50
|
-
properties: {
|
|
51
|
-
items: {
|
|
52
|
-
type: 'array',
|
|
53
|
-
items: getDefinition(ResourceResponseSchema, 'ResourceResponseSchema'),
|
|
54
|
-
},
|
|
55
|
-
pagination: {
|
|
56
|
-
type: 'object',
|
|
57
|
-
properties: {
|
|
58
|
-
page: { type: 'integer' },
|
|
59
|
-
limit: { type: 'integer' },
|
|
60
|
-
totalItems: { type: 'integer' },
|
|
61
|
-
totalPages: { type: 'integer' },
|
|
62
|
-
hasNextPage: { type: 'boolean' },
|
|
63
|
-
hasPreviousPage: { type: 'boolean' },
|
|
64
|
-
},
|
|
65
|
-
},
|
|
66
|
-
},
|
|
67
|
-
},
|
|
68
|
-
},
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Single resource response schema
|
|
73
|
-
*/
|
|
74
|
-
const ResourceSuccessResponseSchema = {
|
|
75
|
-
type: 'object',
|
|
76
|
-
properties: {
|
|
77
|
-
success: { type: 'boolean', enum: [true] },
|
|
78
|
-
message: { type: 'string' },
|
|
79
|
-
data: getDefinition(ResourceResponseSchema, 'ResourceResponseSchema'),
|
|
80
|
-
},
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Resource with owner response schema
|
|
85
|
-
*/
|
|
86
|
-
const ResourceWithOwnerSuccessResponseSchema = {
|
|
87
|
-
type: 'object',
|
|
88
|
-
properties: {
|
|
89
|
-
success: { type: 'boolean', enum: [true] },
|
|
90
|
-
message: { type: 'string' },
|
|
91
|
-
data: getDefinition(ResourceWithOwnerResponseSchema, 'ResourceWithOwnerResponseSchema'),
|
|
92
|
-
},
|
|
93
|
-
};
|
|
94
12
|
|
|
95
13
|
/**
|
|
96
14
|
* Register resources routes
|
|
97
|
-
*
|
|
15
|
+
*
|
|
98
16
|
* @param fastify - Fastify instance
|
|
99
17
|
*/
|
|
100
18
|
export async function resourceRoutes(fastify: FastifyInstance): Promise<void> {
|
|
101
19
|
/**
|
|
102
20
|
* GET /resources
|
|
103
|
-
*
|
|
21
|
+
*
|
|
104
22
|
* Get paginated list of resources with optional filters
|
|
105
23
|
*/
|
|
106
24
|
fastify.get('/', {
|
|
107
|
-
schema: {
|
|
108
|
-
description: 'Get paginated list of resources with optional filters',
|
|
109
|
-
tags: ['resources'],
|
|
110
|
-
summary: 'List resources',
|
|
111
|
-
querystring: {
|
|
112
|
-
type: 'object',
|
|
113
|
-
properties: {
|
|
114
|
-
...(toJsonSchema(ResourceFiltersSchema, 'ResourceFiltersSchema') as any).properties,
|
|
115
|
-
...(toJsonSchema(PaginationSchema, 'PaginationSchema') as any).properties,
|
|
116
|
-
},
|
|
117
|
-
},
|
|
118
|
-
response: {
|
|
119
|
-
200: {
|
|
120
|
-
description: 'Resources retrieved successfully',
|
|
121
|
-
...PaginatedResourcesResponseSchema,
|
|
122
|
-
},
|
|
123
|
-
},
|
|
124
|
-
},
|
|
125
25
|
config: {
|
|
126
26
|
rateLimit: {
|
|
127
27
|
max: 100,
|
|
@@ -133,28 +33,11 @@ export async function resourceRoutes(fastify: FastifyInstance): Promise<void> {
|
|
|
133
33
|
|
|
134
34
|
/**
|
|
135
35
|
* GET /resources/my
|
|
136
|
-
*
|
|
36
|
+
*
|
|
137
37
|
* Get current user's resources
|
|
138
38
|
* Requires authentication
|
|
139
39
|
*/
|
|
140
40
|
fastify.get('/my', {
|
|
141
|
-
schema: {
|
|
142
|
-
description: "Get current user's resources",
|
|
143
|
-
tags: ['resources'],
|
|
144
|
-
summary: 'Get my resources',
|
|
145
|
-
security: [{ bearerAuth: [] }],
|
|
146
|
-
querystring: toJsonSchema(PaginationSchema, 'PaginationSchema'),
|
|
147
|
-
response: {
|
|
148
|
-
200: {
|
|
149
|
-
description: 'User resources retrieved successfully',
|
|
150
|
-
...PaginatedResourcesResponseSchema,
|
|
151
|
-
},
|
|
152
|
-
401: {
|
|
153
|
-
description: 'Unauthorized - Invalid or missing token',
|
|
154
|
-
...ErrorResponseSchema,
|
|
155
|
-
},
|
|
156
|
-
},
|
|
157
|
-
},
|
|
158
41
|
config: {
|
|
159
42
|
rateLimit: {
|
|
160
43
|
max: 1000,
|
|
@@ -167,36 +50,10 @@ export async function resourceRoutes(fastify: FastifyInstance): Promise<void> {
|
|
|
167
50
|
|
|
168
51
|
/**
|
|
169
52
|
* GET /resources/:id
|
|
170
|
-
*
|
|
53
|
+
*
|
|
171
54
|
* Get single resource by ID with owner information
|
|
172
55
|
*/
|
|
173
56
|
fastify.get('/:id', {
|
|
174
|
-
schema: {
|
|
175
|
-
description: 'Get single resource by ID with owner information',
|
|
176
|
-
tags: ['resources'],
|
|
177
|
-
summary: 'Get resource by ID',
|
|
178
|
-
params: {
|
|
179
|
-
type: 'object',
|
|
180
|
-
properties: {
|
|
181
|
-
id: {
|
|
182
|
-
type: 'string',
|
|
183
|
-
format: 'uuid',
|
|
184
|
-
description: 'Resource ID',
|
|
185
|
-
},
|
|
186
|
-
},
|
|
187
|
-
required: ['id'],
|
|
188
|
-
},
|
|
189
|
-
response: {
|
|
190
|
-
200: {
|
|
191
|
-
description: 'Resource retrieved successfully',
|
|
192
|
-
...ResourceWithOwnerSuccessResponseSchema,
|
|
193
|
-
},
|
|
194
|
-
404: {
|
|
195
|
-
description: 'Resource not found',
|
|
196
|
-
...ErrorResponseSchema,
|
|
197
|
-
},
|
|
198
|
-
},
|
|
199
|
-
},
|
|
200
57
|
config: {
|
|
201
58
|
rateLimit: {
|
|
202
59
|
max: 100,
|
|
@@ -208,32 +65,11 @@ export async function resourceRoutes(fastify: FastifyInstance): Promise<void> {
|
|
|
208
65
|
|
|
209
66
|
/**
|
|
210
67
|
* POST /resources
|
|
211
|
-
*
|
|
68
|
+
*
|
|
212
69
|
* Create a new resource
|
|
213
70
|
* Requires authentication
|
|
214
71
|
*/
|
|
215
72
|
fastify.post('/', {
|
|
216
|
-
schema: {
|
|
217
|
-
description: 'Create a new resource',
|
|
218
|
-
tags: ['resources'],
|
|
219
|
-
summary: 'Create resource',
|
|
220
|
-
security: [{ bearerAuth: [] }],
|
|
221
|
-
body: toJsonSchema(CreateResourceSchema, 'CreateResourceRequest'),
|
|
222
|
-
response: {
|
|
223
|
-
201: {
|
|
224
|
-
description: 'Resource created successfully',
|
|
225
|
-
...ResourceSuccessResponseSchema,
|
|
226
|
-
},
|
|
227
|
-
400: {
|
|
228
|
-
description: 'Validation error',
|
|
229
|
-
...ErrorResponseSchema,
|
|
230
|
-
},
|
|
231
|
-
401: {
|
|
232
|
-
description: 'Unauthorized - Invalid or missing token',
|
|
233
|
-
...ErrorResponseSchema,
|
|
234
|
-
},
|
|
235
|
-
},
|
|
236
|
-
},
|
|
237
73
|
config: {
|
|
238
74
|
rateLimit: {
|
|
239
75
|
max: 1000,
|
|
@@ -246,51 +82,11 @@ export async function resourceRoutes(fastify: FastifyInstance): Promise<void> {
|
|
|
246
82
|
|
|
247
83
|
/**
|
|
248
84
|
* PATCH /resources/:id
|
|
249
|
-
*
|
|
85
|
+
*
|
|
250
86
|
* Update a resource (owner only)
|
|
251
87
|
* Requires authentication
|
|
252
88
|
*/
|
|
253
89
|
fastify.patch('/:id', {
|
|
254
|
-
schema: {
|
|
255
|
-
description: 'Update a resource (owner only)',
|
|
256
|
-
tags: ['resources'],
|
|
257
|
-
summary: 'Update resource',
|
|
258
|
-
security: [{ bearerAuth: [] }],
|
|
259
|
-
params: {
|
|
260
|
-
type: 'object',
|
|
261
|
-
properties: {
|
|
262
|
-
id: {
|
|
263
|
-
type: 'string',
|
|
264
|
-
format: 'uuid',
|
|
265
|
-
description: 'Resource ID',
|
|
266
|
-
},
|
|
267
|
-
},
|
|
268
|
-
required: ['id'],
|
|
269
|
-
},
|
|
270
|
-
body: toJsonSchema(UpdateResourceSchema, 'UpdateResourceRequest'),
|
|
271
|
-
response: {
|
|
272
|
-
200: {
|
|
273
|
-
description: 'Resource updated successfully',
|
|
274
|
-
...ResourceSuccessResponseSchema,
|
|
275
|
-
},
|
|
276
|
-
400: {
|
|
277
|
-
description: 'Validation error',
|
|
278
|
-
...ErrorResponseSchema,
|
|
279
|
-
},
|
|
280
|
-
401: {
|
|
281
|
-
description: 'Unauthorized - Invalid or missing token',
|
|
282
|
-
...ErrorResponseSchema,
|
|
283
|
-
},
|
|
284
|
-
403: {
|
|
285
|
-
description: 'Forbidden - You do not own this resource',
|
|
286
|
-
...ErrorResponseSchema,
|
|
287
|
-
},
|
|
288
|
-
404: {
|
|
289
|
-
description: 'Resource not found',
|
|
290
|
-
...ErrorResponseSchema,
|
|
291
|
-
},
|
|
292
|
-
},
|
|
293
|
-
},
|
|
294
90
|
config: {
|
|
295
91
|
rateLimit: {
|
|
296
92
|
max: 1000,
|
|
@@ -303,46 +99,11 @@ export async function resourceRoutes(fastify: FastifyInstance): Promise<void> {
|
|
|
303
99
|
|
|
304
100
|
/**
|
|
305
101
|
* DELETE /resources/:id
|
|
306
|
-
*
|
|
102
|
+
*
|
|
307
103
|
* Delete a resource (owner only, soft delete)
|
|
308
104
|
* Requires authentication
|
|
309
105
|
*/
|
|
310
106
|
fastify.delete('/:id', {
|
|
311
|
-
schema: {
|
|
312
|
-
description: 'Delete a resource (owner only, soft delete)',
|
|
313
|
-
tags: ['resources'],
|
|
314
|
-
summary: 'Delete resource',
|
|
315
|
-
security: [{ bearerAuth: [] }],
|
|
316
|
-
params: {
|
|
317
|
-
type: 'object',
|
|
318
|
-
properties: {
|
|
319
|
-
id: {
|
|
320
|
-
type: 'string',
|
|
321
|
-
format: 'uuid',
|
|
322
|
-
description: 'Resource ID',
|
|
323
|
-
},
|
|
324
|
-
},
|
|
325
|
-
required: ['id'],
|
|
326
|
-
},
|
|
327
|
-
response: {
|
|
328
|
-
200: {
|
|
329
|
-
description: 'Resource deleted successfully',
|
|
330
|
-
...ResourceSuccessResponseSchema,
|
|
331
|
-
},
|
|
332
|
-
401: {
|
|
333
|
-
description: 'Unauthorized - Invalid or missing token',
|
|
334
|
-
...ErrorResponseSchema,
|
|
335
|
-
},
|
|
336
|
-
403: {
|
|
337
|
-
description: 'Forbidden - You do not own this resource',
|
|
338
|
-
...ErrorResponseSchema,
|
|
339
|
-
},
|
|
340
|
-
404: {
|
|
341
|
-
description: 'Resource not found',
|
|
342
|
-
...ErrorResponseSchema,
|
|
343
|
-
},
|
|
344
|
-
},
|
|
345
|
-
},
|
|
346
107
|
config: {
|
|
347
108
|
rateLimit: {
|
|
348
109
|
max: 1000,
|
|
@@ -1,168 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
trigger: always_on
|
|
3
|
-
---
|
|
4
|
-
|
|
5
|
-
> **SCOPE**: These rules apply specifically to the **server** directory.
|
|
6
|
-
|
|
7
|
-
# API Documentation
|
|
8
|
-
|
|
9
|
-
## Stack
|
|
10
|
-
|
|
11
|
-
```json
|
|
12
|
-
{
|
|
13
|
-
"dependencies": {
|
|
14
|
-
"@fastify/swagger": "^8.12.0",
|
|
15
|
-
"@fastify/swagger-ui": "^2.0.0",
|
|
16
|
-
"zod-to-json-schema": "^3.22.0"
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
## Setup
|
|
22
|
-
|
|
23
|
-
```typescript
|
|
24
|
-
// app.ts
|
|
25
|
-
import swagger from '@fastify/swagger';
|
|
26
|
-
import swaggerUI from '@fastify/swagger-ui';
|
|
27
|
-
|
|
28
|
-
await app.register(swagger, {
|
|
29
|
-
openapi: {
|
|
30
|
-
info: {
|
|
31
|
-
title: 'API Server',
|
|
32
|
-
version: '1.0.0',
|
|
33
|
-
},
|
|
34
|
-
servers: [
|
|
35
|
-
{ url: 'http://localhost:3000', description: 'Development' },
|
|
36
|
-
],
|
|
37
|
-
tags: [
|
|
38
|
-
{ name: 'auth', description: 'Authentication' },
|
|
39
|
-
{ name: 'resources', description: 'Resource operations' },
|
|
40
|
-
],
|
|
41
|
-
components: {
|
|
42
|
-
securitySchemes: {
|
|
43
|
-
bearerAuth: { type: 'http', scheme: 'bearer', bearerFormat: 'JWT' },
|
|
44
|
-
},
|
|
45
|
-
},
|
|
46
|
-
},
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
await app.register(swaggerUI, {
|
|
50
|
-
routePrefix: '/docs',
|
|
51
|
-
uiConfig: { docExpansion: 'list', deepLinking: true },
|
|
52
|
-
});
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
**Access:** `http://localhost:3000/docs`
|
|
56
|
-
|
|
57
|
-
## Route Documentation Pattern
|
|
58
|
-
|
|
59
|
-
```typescript
|
|
60
|
-
// modules/resources/resources.schemas.ts
|
|
61
|
-
import { z } from 'zod';
|
|
62
|
-
|
|
63
|
-
export const CreateResourceSchema = z.object({
|
|
64
|
-
title: z.string().min(1).max(200),
|
|
65
|
-
price: z.number().positive(),
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
export const ResourceResponseSchema = z.object({
|
|
69
|
-
success: z.literal(true),
|
|
70
|
-
message: z.string(),
|
|
71
|
-
data: z.object({
|
|
72
|
-
id: z.string().uuid(),
|
|
73
|
-
title: z.string(),
|
|
74
|
-
price: z.number(),
|
|
75
|
-
}),
|
|
76
|
-
});
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
```typescript
|
|
80
|
-
// modules/resources/resources.routes.ts
|
|
81
|
-
import { zodToJsonSchema } from 'zod-to-json-schema';
|
|
82
|
-
import * as schemas from './resources.schemas';
|
|
83
|
-
|
|
84
|
-
app.post('/resources', {
|
|
85
|
-
schema: {
|
|
86
|
-
description: 'Create a new resource',
|
|
87
|
-
tags: ['resources'],
|
|
88
|
-
summary: 'Create resource',
|
|
89
|
-
body: zodToJsonSchema(schemas.CreateResourceSchema, 'CreateResource'),
|
|
90
|
-
response: {
|
|
91
|
-
201: zodToJsonSchema(schemas.ResourceResponseSchema, 'ResourceResponse'),
|
|
92
|
-
400: {
|
|
93
|
-
description: 'Validation error',
|
|
94
|
-
type: 'object',
|
|
95
|
-
properties: {
|
|
96
|
-
success: { type: 'boolean', enum: [false] },
|
|
97
|
-
error: {
|
|
98
|
-
type: 'object',
|
|
99
|
-
properties: {
|
|
100
|
-
code: { type: 'string' },
|
|
101
|
-
message: { type: 'string' },
|
|
102
|
-
},
|
|
103
|
-
},
|
|
104
|
-
},
|
|
105
|
-
},
|
|
106
|
-
},
|
|
107
|
-
security: [{ bearerAuth: [] }],
|
|
108
|
-
},
|
|
109
|
-
handler: resourceController.createResource,
|
|
110
|
-
});
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
## Document All Responses
|
|
114
|
-
|
|
115
|
-
```typescript
|
|
116
|
-
response: {
|
|
117
|
-
200: successSchema,
|
|
118
|
-
400: validationErrorSchema,
|
|
119
|
-
401: unauthorizedSchema,
|
|
120
|
-
404: notFoundSchema,
|
|
121
|
-
500: internalErrorSchema,
|
|
122
|
-
}
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
## Production Config
|
|
126
|
-
|
|
127
|
-
```typescript
|
|
128
|
-
if (process.env.NODE_ENV === 'production') {
|
|
129
|
-
// Option 1: Disable docs
|
|
130
|
-
// Don't register swagger-ui
|
|
131
|
-
|
|
132
|
-
// Option 2: Require auth for docs
|
|
133
|
-
await app.register(swaggerUI, {
|
|
134
|
-
routePrefix: '/docs',
|
|
135
|
-
transformSpecification: (swaggerObject, request, reply) => {
|
|
136
|
-
if (!request.headers.authorization) {
|
|
137
|
-
reply.code(401).send({ error: 'Unauthorized' });
|
|
138
|
-
}
|
|
139
|
-
return swaggerObject;
|
|
140
|
-
},
|
|
141
|
-
});
|
|
142
|
-
}
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
## Best Practices
|
|
146
|
-
|
|
147
|
-
### ✅ DO:
|
|
148
|
-
- Use Zod schemas for all routes
|
|
149
|
-
- Document all error responses
|
|
150
|
-
- Add descriptions and summaries
|
|
151
|
-
- Include security requirements
|
|
152
|
-
- Tag routes by domain
|
|
153
|
-
|
|
154
|
-
### ❌ DON'T:
|
|
155
|
-
- Skip documentation on routes
|
|
156
|
-
- Forget error response schemas
|
|
157
|
-
- Expose docs publicly in production
|
|
158
|
-
- Use generic descriptions
|
|
159
|
-
|
|
160
|
-
## Checklist
|
|
161
|
-
|
|
162
|
-
- [ ] Swagger and Swagger UI registered
|
|
163
|
-
- [ ] All routes have schemas
|
|
164
|
-
- [ ] Zod schemas converted to JSON Schema
|
|
165
|
-
- [ ] Error responses documented
|
|
166
|
-
- [ ] Tags used for grouping
|
|
167
|
-
- [ ] Security requirements documented
|
|
168
|
-
- [ ] Production docs secured
|