create-tigra 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.
- package/LICENSE +21 -0
- package/README.md +87 -0
- package/bin/create-tigra.js +292 -0
- package/package.json +41 -0
- package/template/.agent/rules/client/01-project-structure.md +326 -0
- package/template/.agent/rules/client/02-component-patterns.md +249 -0
- package/template/.agent/rules/client/03-typescript-rules.md +226 -0
- package/template/.agent/rules/client/04-state-management.md +474 -0
- package/template/.agent/rules/client/05-api-integration.md +129 -0
- package/template/.agent/rules/client/06-forms-validation.md +129 -0
- package/template/.agent/rules/client/07-common-patterns.md +150 -0
- package/template/.agent/rules/client/08-color-system.md +93 -0
- package/template/.agent/rules/client/09-security-rules.md +97 -0
- package/template/.agent/rules/client/10-testing-strategy.md +370 -0
- package/template/.agent/rules/global/ai-edit-safety.md +38 -0
- package/template/.agent/rules/server/01-db-and-migrations.md +242 -0
- package/template/.agent/rules/server/02-general-rules.md +111 -0
- package/template/.agent/rules/server/03-migrations.md +20 -0
- package/template/.agent/rules/server/04-pagination.md +130 -0
- package/template/.agent/rules/server/05-project-conventions.md +71 -0
- package/template/.agent/rules/server/06-response-handling.md +173 -0
- package/template/.agent/rules/server/07-testing-strategy.md +506 -0
- package/template/.agent/rules/server/08-observability.md +180 -0
- package/template/.agent/rules/server/09-api-documentation-v2.md +168 -0
- package/template/.agent/rules/server/10-background-jobs-v2.md +185 -0
- package/template/.agent/rules/server/11-rate-limiting-v2.md +210 -0
- package/template/.agent/rules/server/12-performance-optimization.md +567 -0
- package/template/.claude/rules/client-01-project-structure.md +327 -0
- package/template/.claude/rules/client-02-component-patterns.md +250 -0
- package/template/.claude/rules/client-03-typescript-rules.md +227 -0
- package/template/.claude/rules/client-04-state-management.md +475 -0
- package/template/.claude/rules/client-05-api-integration.md +130 -0
- package/template/.claude/rules/client-06-forms-validation.md +130 -0
- package/template/.claude/rules/client-07-common-patterns.md +151 -0
- package/template/.claude/rules/client-08-color-system.md +94 -0
- package/template/.claude/rules/client-09-security-rules.md +98 -0
- package/template/.claude/rules/client-10-testing-strategy.md +371 -0
- package/template/.claude/rules/global-ai-edit-safety.md +39 -0
- package/template/.claude/rules/server-01-db-and-migrations.md +243 -0
- package/template/.claude/rules/server-02-general-rules.md +112 -0
- package/template/.claude/rules/server-03-migrations.md +21 -0
- package/template/.claude/rules/server-04-pagination.md +131 -0
- package/template/.claude/rules/server-05-project-conventions.md +72 -0
- package/template/.claude/rules/server-06-response-handling.md +174 -0
- package/template/.claude/rules/server-07-testing-strategy.md +507 -0
- package/template/.claude/rules/server-08-observability.md +181 -0
- package/template/.claude/rules/server-09-api-documentation-v2.md +169 -0
- package/template/.claude/rules/server-10-background-jobs-v2.md +186 -0
- package/template/.claude/rules/server-11-rate-limiting-v2.md +211 -0
- package/template/.claude/rules/server-12-performance-optimization.md +568 -0
- package/template/.cursor/rules/client-01-project-structure.mdc +327 -0
- package/template/.cursor/rules/client-02-component-patterns.mdc +250 -0
- package/template/.cursor/rules/client-03-typescript-rules.mdc +227 -0
- package/template/.cursor/rules/client-04-state-management.mdc +475 -0
- package/template/.cursor/rules/client-05-api-integration.mdc +130 -0
- package/template/.cursor/rules/client-06-forms-validation.mdc +130 -0
- package/template/.cursor/rules/client-07-common-patterns.mdc +151 -0
- package/template/.cursor/rules/client-08-color-system.mdc +94 -0
- package/template/.cursor/rules/client-09-security-rules.mdc +98 -0
- package/template/.cursor/rules/client-10-testing-strategy.mdc +371 -0
- package/template/.cursor/rules/global-ai-edit-safety.mdc +39 -0
- package/template/.cursor/rules/server-01-db-and-migrations.mdc +243 -0
- package/template/.cursor/rules/server-02-general-rules.mdc +112 -0
- package/template/.cursor/rules/server-03-migrations.mdc +21 -0
- package/template/.cursor/rules/server-04-pagination.mdc +131 -0
- package/template/.cursor/rules/server-05-project-conventions.mdc +72 -0
- package/template/.cursor/rules/server-06-response-handling.mdc +174 -0
- package/template/.cursor/rules/server-07-testing-strategy.mdc +507 -0
- package/template/.cursor/rules/server-08-observability.mdc +181 -0
- package/template/.cursor/rules/server-09-api-documentation-v2.mdc +169 -0
- package/template/.cursor/rules/server-10-background-jobs-v2.mdc +186 -0
- package/template/.cursor/rules/server-11-rate-limiting-v2.mdc +211 -0
- package/template/.cursor/rules/server-12-performance-optimization.mdc +568 -0
- package/template/CLAUDE.md +207 -0
- package/template/server/.env.example +148 -0
- package/template/server/.tsc-aliasrc.json +12 -0
- package/template/server/README.md +175 -0
- package/template/server/SECURITY.md +190 -0
- package/template/server/biome.json +42 -0
- package/template/server/docker-compose.yml +111 -0
- package/template/server/package.json +83 -0
- package/template/server/postman_collection.json +733 -0
- package/template/server/prisma/schema.prisma +92 -0
- package/template/server/prisma/seed.ts +142 -0
- package/template/server/scripts/wait-for-db.js +60 -0
- package/template/server/src/app.ts +74 -0
- package/template/server/src/config/env.ts +101 -0
- package/template/server/src/hooks/request-timing.hook.ts +26 -0
- package/template/server/src/libs/auth/authenticate.middleware.ts +22 -0
- package/template/server/src/libs/auth/rbac.middleware.test.ts +134 -0
- package/template/server/src/libs/auth/rbac.middleware.ts +147 -0
- package/template/server/src/libs/db.ts +76 -0
- package/template/server/src/libs/error-handler.ts +89 -0
- package/template/server/src/libs/logger.ts +60 -0
- package/template/server/src/libs/queue.ts +79 -0
- package/template/server/src/libs/redis.ts +79 -0
- package/template/server/src/libs/swagger-schemas.ts +16 -0
- package/template/server/src/modules/admin/admin.controller.ts +122 -0
- package/template/server/src/modules/admin/admin.routes.ts +100 -0
- package/template/server/src/modules/admin/admin.schemas.ts +35 -0
- package/template/server/src/modules/admin/admin.service.ts +167 -0
- package/template/server/src/modules/auth/auth.controller.ts +141 -0
- package/template/server/src/modules/auth/auth.integration.test.ts +150 -0
- package/template/server/src/modules/auth/auth.repo.ts +218 -0
- package/template/server/src/modules/auth/auth.routes.ts +204 -0
- package/template/server/src/modules/auth/auth.schemas.ts +137 -0
- package/template/server/src/modules/auth/auth.service.test.ts +119 -0
- package/template/server/src/modules/auth/auth.service.ts +329 -0
- package/template/server/src/modules/auth/auth.types.ts +97 -0
- package/template/server/src/modules/resources/resources.controller.ts +218 -0
- package/template/server/src/modules/resources/resources.repo.ts +253 -0
- package/template/server/src/modules/resources/resources.routes.ts +355 -0
- package/template/server/src/modules/resources/resources.schemas.ts +146 -0
- package/template/server/src/modules/resources/resources.service.ts +218 -0
- package/template/server/src/modules/resources/resources.types.ts +73 -0
- package/template/server/src/plugins/rate-limit.plugin.ts +21 -0
- package/template/server/src/plugins/security.plugin.ts +21 -0
- package/template/server/src/plugins/swagger.plugin.ts +41 -0
- package/template/server/src/routes/health.routes.ts +31 -0
- package/template/server/src/server.ts +142 -0
- package/template/server/src/test/setup.ts +38 -0
- package/template/server/src/types/fastify.d.ts +36 -0
- package/template/server/src/utils/errors.ts +108 -0
- package/template/server/src/utils/pagination.ts +120 -0
- package/template/server/src/utils/response.ts +110 -0
- package/template/server/src/workers/file.worker.ts +106 -0
- package/template/server/tsconfig.build.json +30 -0
- package/template/server/tsconfig.build.tsbuildinfo +1 -0
- package/template/server/tsconfig.json +89 -0
- package/template/server/tsconfig.test.json +22 -0
- package/template/server/vitest.config.ts +98 -0
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resources Repository
|
|
3
|
+
*
|
|
4
|
+
* Database operations for resources module.
|
|
5
|
+
* Handles all Prisma queries related to resources.
|
|
6
|
+
*
|
|
7
|
+
* @see /mnt/project/02-general-rules.md
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { prisma } from '@/libs/db';
|
|
11
|
+
import type { Resource as PrismaResource } from '@prisma/client';
|
|
12
|
+
import type { ResourceFilters, Resource, ResourceWithOwner } from './resources.types';
|
|
13
|
+
import type { CreateResourceInput, UpdateResourceInput } from './resources.schemas';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Find many resources with filters and pagination
|
|
17
|
+
*
|
|
18
|
+
* @param filters - Filter criteria
|
|
19
|
+
* @param page - Page number (1-indexed)
|
|
20
|
+
* @param limit - Items per page
|
|
21
|
+
* @returns Array of resources
|
|
22
|
+
*/
|
|
23
|
+
export async function findMany(
|
|
24
|
+
filters: ResourceFilters,
|
|
25
|
+
page: number,
|
|
26
|
+
limit: number
|
|
27
|
+
): Promise<Resource[]> {
|
|
28
|
+
// Build where clause
|
|
29
|
+
const where: any = {};
|
|
30
|
+
|
|
31
|
+
// Status filter
|
|
32
|
+
if (filters.status) {
|
|
33
|
+
where.status = filters.status;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Price range filters
|
|
37
|
+
if (filters.minPrice !== undefined || filters.maxPrice !== undefined) {
|
|
38
|
+
where.price = {};
|
|
39
|
+
if (filters.minPrice !== undefined) {
|
|
40
|
+
where.price.gte = filters.minPrice;
|
|
41
|
+
}
|
|
42
|
+
if (filters.maxPrice !== undefined) {
|
|
43
|
+
where.price.lte = filters.maxPrice;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Owner filter
|
|
48
|
+
if (filters.ownerId) {
|
|
49
|
+
where.ownerId = filters.ownerId;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Search filter (title contains)
|
|
53
|
+
if (filters.search) {
|
|
54
|
+
where.title = {
|
|
55
|
+
contains: filters.search,
|
|
56
|
+
mode: 'insensitive', // Case-insensitive search
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Calculate offset
|
|
61
|
+
const skip = (page - 1) * limit;
|
|
62
|
+
|
|
63
|
+
// Execute query
|
|
64
|
+
const resources = await prisma.resource.findMany({
|
|
65
|
+
where,
|
|
66
|
+
skip,
|
|
67
|
+
take: limit,
|
|
68
|
+
orderBy: {
|
|
69
|
+
createdAt: 'desc',
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
return resources;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Count resources with filters
|
|
78
|
+
*
|
|
79
|
+
* @param filters - Filter criteria
|
|
80
|
+
* @returns Total count of resources matching filters
|
|
81
|
+
*/
|
|
82
|
+
export async function count(filters: ResourceFilters): Promise<number> {
|
|
83
|
+
// Build where clause (same as findMany)
|
|
84
|
+
const where: any = {};
|
|
85
|
+
|
|
86
|
+
if (filters.status) {
|
|
87
|
+
where.status = filters.status;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (filters.minPrice !== undefined || filters.maxPrice !== undefined) {
|
|
91
|
+
where.price = {};
|
|
92
|
+
if (filters.minPrice !== undefined) {
|
|
93
|
+
where.price.gte = filters.minPrice;
|
|
94
|
+
}
|
|
95
|
+
if (filters.maxPrice !== undefined) {
|
|
96
|
+
where.price.lte = filters.maxPrice;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (filters.ownerId) {
|
|
101
|
+
where.ownerId = filters.ownerId;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (filters.search) {
|
|
105
|
+
where.title = {
|
|
106
|
+
contains: filters.search,
|
|
107
|
+
mode: 'insensitive',
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Execute count query
|
|
112
|
+
const total = await prisma.resource.count({ where });
|
|
113
|
+
|
|
114
|
+
return total;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Find resource by ID
|
|
119
|
+
*
|
|
120
|
+
* @param id - Resource ID
|
|
121
|
+
* @returns Resource with owner relation, or null if not found
|
|
122
|
+
*/
|
|
123
|
+
export async function findById(id: string): Promise<ResourceWithOwner | null> {
|
|
124
|
+
const resource = await prisma.resource.findUnique({
|
|
125
|
+
where: { id },
|
|
126
|
+
include: {
|
|
127
|
+
owner: {
|
|
128
|
+
select: {
|
|
129
|
+
id: true,
|
|
130
|
+
email: true,
|
|
131
|
+
name: true,
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
return resource;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Create a new resource
|
|
142
|
+
*
|
|
143
|
+
* @param data - Resource creation data
|
|
144
|
+
* @param ownerId - Owner user ID
|
|
145
|
+
* @returns Created resource
|
|
146
|
+
*/
|
|
147
|
+
export async function create(
|
|
148
|
+
data: CreateResourceInput,
|
|
149
|
+
ownerId: string
|
|
150
|
+
): Promise<Resource> {
|
|
151
|
+
const resource = await prisma.resource.create({
|
|
152
|
+
data: {
|
|
153
|
+
title: data.title,
|
|
154
|
+
summary: data.summary || null,
|
|
155
|
+
price: data.price,
|
|
156
|
+
ownerId,
|
|
157
|
+
status: 'active',
|
|
158
|
+
},
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
return resource;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Update a resource
|
|
166
|
+
*
|
|
167
|
+
* @param id - Resource ID
|
|
168
|
+
* @param data - Update data (partial)
|
|
169
|
+
* @returns Updated resource
|
|
170
|
+
*/
|
|
171
|
+
export async function update(
|
|
172
|
+
id: string,
|
|
173
|
+
data: UpdateResourceInput
|
|
174
|
+
): Promise<Resource> {
|
|
175
|
+
const resource = await prisma.resource.update({
|
|
176
|
+
where: { id },
|
|
177
|
+
data: {
|
|
178
|
+
...(data.title !== undefined && { title: data.title }),
|
|
179
|
+
...(data.summary !== undefined && { summary: data.summary }),
|
|
180
|
+
...(data.price !== undefined && { price: data.price }),
|
|
181
|
+
},
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
return resource;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Delete a resource (soft delete)
|
|
189
|
+
*
|
|
190
|
+
* Sets status to 'deleted' instead of removing from database.
|
|
191
|
+
*
|
|
192
|
+
* @param id - Resource ID
|
|
193
|
+
* @returns Deleted resource
|
|
194
|
+
*/
|
|
195
|
+
export async function deleteResource(id: string): Promise<Resource> {
|
|
196
|
+
const resource = await prisma.resource.update({
|
|
197
|
+
where: { id },
|
|
198
|
+
data: {
|
|
199
|
+
status: 'deleted',
|
|
200
|
+
},
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
return resource;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Hard delete a resource
|
|
208
|
+
*
|
|
209
|
+
* Permanently removes resource from database.
|
|
210
|
+
* Use with caution - prefer soft delete.
|
|
211
|
+
*
|
|
212
|
+
* @param id - Resource ID
|
|
213
|
+
*/
|
|
214
|
+
export async function hardDelete(id: string): Promise<void> {
|
|
215
|
+
await prisma.resource.delete({
|
|
216
|
+
where: { id },
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Check if resource exists
|
|
222
|
+
*
|
|
223
|
+
* @param id - Resource ID
|
|
224
|
+
* @returns True if resource exists, false otherwise
|
|
225
|
+
*/
|
|
226
|
+
export async function exists(id: string): Promise<boolean> {
|
|
227
|
+
const count = await prisma.resource.count({
|
|
228
|
+
where: { id },
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
return count > 0;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Check if user owns resource
|
|
236
|
+
*
|
|
237
|
+
* @param resourceId - Resource ID
|
|
238
|
+
* @param userId - User ID
|
|
239
|
+
* @returns True if user owns resource, false otherwise
|
|
240
|
+
*/
|
|
241
|
+
export async function isOwner(
|
|
242
|
+
resourceId: string,
|
|
243
|
+
userId: string
|
|
244
|
+
): Promise<boolean> {
|
|
245
|
+
const count = await prisma.resource.count({
|
|
246
|
+
where: {
|
|
247
|
+
id: resourceId,
|
|
248
|
+
ownerId: userId,
|
|
249
|
+
},
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
return count > 0;
|
|
253
|
+
}
|
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resources Routes
|
|
3
|
+
*
|
|
4
|
+
* Fastify route definitions for resources endpoints.
|
|
5
|
+
* Includes comprehensive Swagger documentation and rate limiting.
|
|
6
|
+
*
|
|
7
|
+
* @see /mnt/project/09-api-documentation-v2.md
|
|
8
|
+
* @see /mnt/project/11-rate-limiting-v2.md
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { FastifyInstance } from 'fastify';
|
|
12
|
+
import { toJsonSchema, getDefinition } from '@/libs/swagger-schemas';
|
|
13
|
+
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
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Register resources routes
|
|
97
|
+
*
|
|
98
|
+
* @param fastify - Fastify instance
|
|
99
|
+
*/
|
|
100
|
+
export async function resourceRoutes(fastify: FastifyInstance): Promise<void> {
|
|
101
|
+
/**
|
|
102
|
+
* GET /resources
|
|
103
|
+
*
|
|
104
|
+
* Get paginated list of resources with optional filters
|
|
105
|
+
*/
|
|
106
|
+
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
|
+
config: {
|
|
126
|
+
rateLimit: {
|
|
127
|
+
max: 100,
|
|
128
|
+
timeWindow: '15 minutes',
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
handler: resourceController.listResources,
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* GET /resources/my
|
|
136
|
+
*
|
|
137
|
+
* Get current user's resources
|
|
138
|
+
* Requires authentication
|
|
139
|
+
*/
|
|
140
|
+
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
|
+
config: {
|
|
159
|
+
rateLimit: {
|
|
160
|
+
max: 1000,
|
|
161
|
+
timeWindow: '15 minutes',
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
preHandler: [fastify.authenticate, fastify.requireAny()],
|
|
165
|
+
handler: resourceController.getMyResources,
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* GET /resources/:id
|
|
170
|
+
*
|
|
171
|
+
* Get single resource by ID with owner information
|
|
172
|
+
*/
|
|
173
|
+
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
|
+
config: {
|
|
201
|
+
rateLimit: {
|
|
202
|
+
max: 100,
|
|
203
|
+
timeWindow: '15 minutes',
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
handler: resourceController.getResource,
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* POST /resources
|
|
211
|
+
*
|
|
212
|
+
* Create a new resource
|
|
213
|
+
* Requires authentication
|
|
214
|
+
*/
|
|
215
|
+
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
|
+
config: {
|
|
238
|
+
rateLimit: {
|
|
239
|
+
max: 1000,
|
|
240
|
+
timeWindow: '15 minutes',
|
|
241
|
+
},
|
|
242
|
+
},
|
|
243
|
+
preHandler: [fastify.authenticate, fastify.requireAny()],
|
|
244
|
+
handler: resourceController.createResource,
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* PATCH /resources/:id
|
|
249
|
+
*
|
|
250
|
+
* Update a resource (owner only)
|
|
251
|
+
* Requires authentication
|
|
252
|
+
*/
|
|
253
|
+
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
|
+
config: {
|
|
295
|
+
rateLimit: {
|
|
296
|
+
max: 1000,
|
|
297
|
+
timeWindow: '15 minutes',
|
|
298
|
+
},
|
|
299
|
+
},
|
|
300
|
+
preHandler: [fastify.authenticate, fastify.requireAny()],
|
|
301
|
+
handler: resourceController.updateResource,
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* DELETE /resources/:id
|
|
306
|
+
*
|
|
307
|
+
* Delete a resource (owner only, soft delete)
|
|
308
|
+
* Requires authentication
|
|
309
|
+
*/
|
|
310
|
+
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
|
+
config: {
|
|
347
|
+
rateLimit: {
|
|
348
|
+
max: 1000,
|
|
349
|
+
timeWindow: '15 minutes',
|
|
350
|
+
},
|
|
351
|
+
},
|
|
352
|
+
preHandler: [fastify.authenticate, fastify.requireAny()],
|
|
353
|
+
handler: resourceController.deleteResource,
|
|
354
|
+
});
|
|
355
|
+
}
|