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.
Files changed (131) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +87 -0
  3. package/bin/create-tigra.js +292 -0
  4. package/package.json +41 -0
  5. package/template/.agent/rules/client/01-project-structure.md +326 -0
  6. package/template/.agent/rules/client/02-component-patterns.md +249 -0
  7. package/template/.agent/rules/client/03-typescript-rules.md +226 -0
  8. package/template/.agent/rules/client/04-state-management.md +474 -0
  9. package/template/.agent/rules/client/05-api-integration.md +129 -0
  10. package/template/.agent/rules/client/06-forms-validation.md +129 -0
  11. package/template/.agent/rules/client/07-common-patterns.md +150 -0
  12. package/template/.agent/rules/client/08-color-system.md +93 -0
  13. package/template/.agent/rules/client/09-security-rules.md +97 -0
  14. package/template/.agent/rules/client/10-testing-strategy.md +370 -0
  15. package/template/.agent/rules/global/ai-edit-safety.md +38 -0
  16. package/template/.agent/rules/server/01-db-and-migrations.md +242 -0
  17. package/template/.agent/rules/server/02-general-rules.md +111 -0
  18. package/template/.agent/rules/server/03-migrations.md +20 -0
  19. package/template/.agent/rules/server/04-pagination.md +130 -0
  20. package/template/.agent/rules/server/05-project-conventions.md +71 -0
  21. package/template/.agent/rules/server/06-response-handling.md +173 -0
  22. package/template/.agent/rules/server/07-testing-strategy.md +506 -0
  23. package/template/.agent/rules/server/08-observability.md +180 -0
  24. package/template/.agent/rules/server/09-api-documentation-v2.md +168 -0
  25. package/template/.agent/rules/server/10-background-jobs-v2.md +185 -0
  26. package/template/.agent/rules/server/11-rate-limiting-v2.md +210 -0
  27. package/template/.agent/rules/server/12-performance-optimization.md +567 -0
  28. package/template/.claude/rules/client-01-project-structure.md +327 -0
  29. package/template/.claude/rules/client-02-component-patterns.md +250 -0
  30. package/template/.claude/rules/client-03-typescript-rules.md +227 -0
  31. package/template/.claude/rules/client-04-state-management.md +475 -0
  32. package/template/.claude/rules/client-05-api-integration.md +130 -0
  33. package/template/.claude/rules/client-06-forms-validation.md +130 -0
  34. package/template/.claude/rules/client-07-common-patterns.md +151 -0
  35. package/template/.claude/rules/client-08-color-system.md +94 -0
  36. package/template/.claude/rules/client-09-security-rules.md +98 -0
  37. package/template/.claude/rules/client-10-testing-strategy.md +371 -0
  38. package/template/.claude/rules/global-ai-edit-safety.md +39 -0
  39. package/template/.claude/rules/server-01-db-and-migrations.md +243 -0
  40. package/template/.claude/rules/server-02-general-rules.md +112 -0
  41. package/template/.claude/rules/server-03-migrations.md +21 -0
  42. package/template/.claude/rules/server-04-pagination.md +131 -0
  43. package/template/.claude/rules/server-05-project-conventions.md +72 -0
  44. package/template/.claude/rules/server-06-response-handling.md +174 -0
  45. package/template/.claude/rules/server-07-testing-strategy.md +507 -0
  46. package/template/.claude/rules/server-08-observability.md +181 -0
  47. package/template/.claude/rules/server-09-api-documentation-v2.md +169 -0
  48. package/template/.claude/rules/server-10-background-jobs-v2.md +186 -0
  49. package/template/.claude/rules/server-11-rate-limiting-v2.md +211 -0
  50. package/template/.claude/rules/server-12-performance-optimization.md +568 -0
  51. package/template/.cursor/rules/client-01-project-structure.mdc +327 -0
  52. package/template/.cursor/rules/client-02-component-patterns.mdc +250 -0
  53. package/template/.cursor/rules/client-03-typescript-rules.mdc +227 -0
  54. package/template/.cursor/rules/client-04-state-management.mdc +475 -0
  55. package/template/.cursor/rules/client-05-api-integration.mdc +130 -0
  56. package/template/.cursor/rules/client-06-forms-validation.mdc +130 -0
  57. package/template/.cursor/rules/client-07-common-patterns.mdc +151 -0
  58. package/template/.cursor/rules/client-08-color-system.mdc +94 -0
  59. package/template/.cursor/rules/client-09-security-rules.mdc +98 -0
  60. package/template/.cursor/rules/client-10-testing-strategy.mdc +371 -0
  61. package/template/.cursor/rules/global-ai-edit-safety.mdc +39 -0
  62. package/template/.cursor/rules/server-01-db-and-migrations.mdc +243 -0
  63. package/template/.cursor/rules/server-02-general-rules.mdc +112 -0
  64. package/template/.cursor/rules/server-03-migrations.mdc +21 -0
  65. package/template/.cursor/rules/server-04-pagination.mdc +131 -0
  66. package/template/.cursor/rules/server-05-project-conventions.mdc +72 -0
  67. package/template/.cursor/rules/server-06-response-handling.mdc +174 -0
  68. package/template/.cursor/rules/server-07-testing-strategy.mdc +507 -0
  69. package/template/.cursor/rules/server-08-observability.mdc +181 -0
  70. package/template/.cursor/rules/server-09-api-documentation-v2.mdc +169 -0
  71. package/template/.cursor/rules/server-10-background-jobs-v2.mdc +186 -0
  72. package/template/.cursor/rules/server-11-rate-limiting-v2.mdc +211 -0
  73. package/template/.cursor/rules/server-12-performance-optimization.mdc +568 -0
  74. package/template/CLAUDE.md +207 -0
  75. package/template/server/.env.example +148 -0
  76. package/template/server/.tsc-aliasrc.json +12 -0
  77. package/template/server/README.md +175 -0
  78. package/template/server/SECURITY.md +190 -0
  79. package/template/server/biome.json +42 -0
  80. package/template/server/docker-compose.yml +111 -0
  81. package/template/server/package.json +83 -0
  82. package/template/server/postman_collection.json +733 -0
  83. package/template/server/prisma/schema.prisma +92 -0
  84. package/template/server/prisma/seed.ts +142 -0
  85. package/template/server/scripts/wait-for-db.js +60 -0
  86. package/template/server/src/app.ts +74 -0
  87. package/template/server/src/config/env.ts +101 -0
  88. package/template/server/src/hooks/request-timing.hook.ts +26 -0
  89. package/template/server/src/libs/auth/authenticate.middleware.ts +22 -0
  90. package/template/server/src/libs/auth/rbac.middleware.test.ts +134 -0
  91. package/template/server/src/libs/auth/rbac.middleware.ts +147 -0
  92. package/template/server/src/libs/db.ts +76 -0
  93. package/template/server/src/libs/error-handler.ts +89 -0
  94. package/template/server/src/libs/logger.ts +60 -0
  95. package/template/server/src/libs/queue.ts +79 -0
  96. package/template/server/src/libs/redis.ts +79 -0
  97. package/template/server/src/libs/swagger-schemas.ts +16 -0
  98. package/template/server/src/modules/admin/admin.controller.ts +122 -0
  99. package/template/server/src/modules/admin/admin.routes.ts +100 -0
  100. package/template/server/src/modules/admin/admin.schemas.ts +35 -0
  101. package/template/server/src/modules/admin/admin.service.ts +167 -0
  102. package/template/server/src/modules/auth/auth.controller.ts +141 -0
  103. package/template/server/src/modules/auth/auth.integration.test.ts +150 -0
  104. package/template/server/src/modules/auth/auth.repo.ts +218 -0
  105. package/template/server/src/modules/auth/auth.routes.ts +204 -0
  106. package/template/server/src/modules/auth/auth.schemas.ts +137 -0
  107. package/template/server/src/modules/auth/auth.service.test.ts +119 -0
  108. package/template/server/src/modules/auth/auth.service.ts +329 -0
  109. package/template/server/src/modules/auth/auth.types.ts +97 -0
  110. package/template/server/src/modules/resources/resources.controller.ts +218 -0
  111. package/template/server/src/modules/resources/resources.repo.ts +253 -0
  112. package/template/server/src/modules/resources/resources.routes.ts +355 -0
  113. package/template/server/src/modules/resources/resources.schemas.ts +146 -0
  114. package/template/server/src/modules/resources/resources.service.ts +218 -0
  115. package/template/server/src/modules/resources/resources.types.ts +73 -0
  116. package/template/server/src/plugins/rate-limit.plugin.ts +21 -0
  117. package/template/server/src/plugins/security.plugin.ts +21 -0
  118. package/template/server/src/plugins/swagger.plugin.ts +41 -0
  119. package/template/server/src/routes/health.routes.ts +31 -0
  120. package/template/server/src/server.ts +142 -0
  121. package/template/server/src/test/setup.ts +38 -0
  122. package/template/server/src/types/fastify.d.ts +36 -0
  123. package/template/server/src/utils/errors.ts +108 -0
  124. package/template/server/src/utils/pagination.ts +120 -0
  125. package/template/server/src/utils/response.ts +110 -0
  126. package/template/server/src/workers/file.worker.ts +106 -0
  127. package/template/server/tsconfig.build.json +30 -0
  128. package/template/server/tsconfig.build.tsbuildinfo +1 -0
  129. package/template/server/tsconfig.json +89 -0
  130. package/template/server/tsconfig.test.json +22 -0
  131. 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
+ }