create-tigra 1.0.7 → 2.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 -21
- package/README.md +80 -87
- package/bin/create-tigra.js +242 -309
- package/package.json +49 -41
- package/template/_claude/QUICK_REFERENCE.md +193 -0
- package/template/_claude/README.md +53 -0
- package/template/_claude/commands/create-client.md +881 -0
- package/template/_claude/commands/create-server.md +383 -0
- package/template/_claude/rules/client/01-project-structure.md +133 -0
- package/template/_claude/rules/client/02-components-and-types.md +146 -0
- package/template/_claude/rules/client/03-data-and-state.md +156 -0
- package/template/_claude/rules/client/04-design-system.md +185 -0
- package/template/_claude/rules/client/05-security.md +55 -0
- package/template/_claude/rules/client/06-ux-checklist.md +81 -0
- package/template/_claude/rules/client/core.md +42 -0
- package/template/_claude/rules/global/core.md +77 -0
- package/template/_claude/rules/server/core.md +50 -0
- package/template/_claude/rules/server/database.md +124 -0
- package/template/_claude/rules/server/project-conventions.md +150 -0
- package/template/_claude/rules/server/response-handling.md +144 -0
- package/template/client/.env.example +5 -0
- package/template/client/README.md +36 -0
- package/template/client/components.json +23 -0
- package/template/client/eslint.config.mjs +18 -0
- package/template/client/next.config.ts +34 -0
- package/template/client/package.json +44 -0
- package/template/client/postcss.config.mjs +7 -0
- package/template/client/src/app/(auth)/layout.tsx +18 -0
- package/template/client/src/app/(auth)/login/page.tsx +13 -0
- package/template/client/src/app/(auth)/register/page.tsx +13 -0
- package/template/client/src/app/(main)/dashboard/page.tsx +22 -0
- package/template/client/src/app/(main)/layout.tsx +11 -0
- package/template/client/src/app/error.tsx +27 -0
- package/template/client/src/app/favicon.ico +0 -0
- package/template/client/src/app/globals.css +145 -0
- package/template/client/src/app/layout.tsx +36 -0
- package/template/client/src/app/loading.tsx +11 -0
- package/template/client/src/app/not-found.tsx +23 -0
- package/template/client/src/app/page.tsx +45 -0
- package/template/client/src/app/providers.tsx +43 -0
- package/template/client/src/components/common/ConfirmDialog.tsx +56 -0
- package/template/client/src/components/common/EmptyState.tsx +31 -0
- package/template/client/src/components/common/LoadingSpinner.tsx +30 -0
- package/template/client/src/components/common/Pagination.tsx +55 -0
- package/template/client/src/components/layout/Footer.tsx +17 -0
- package/template/client/src/components/layout/Header.tsx +173 -0
- package/template/client/src/components/layout/MainLayout.tsx +18 -0
- package/template/client/src/components/ui/alert-dialog.tsx +196 -0
- package/template/client/src/components/ui/badge.tsx +48 -0
- package/template/client/src/components/ui/button.tsx +64 -0
- package/template/client/src/components/ui/card.tsx +92 -0
- package/template/client/src/components/ui/input.tsx +21 -0
- package/template/client/src/components/ui/label.tsx +24 -0
- package/template/client/src/components/ui/select.tsx +190 -0
- package/template/client/src/components/ui/skeleton.tsx +13 -0
- package/template/client/src/components/ui/table.tsx +116 -0
- package/template/client/src/features/auth/components/AuthInitializer.tsx +55 -0
- package/template/client/src/features/auth/components/LoginForm.tsx +107 -0
- package/template/client/src/features/auth/components/RegisterForm.tsx +178 -0
- package/template/client/src/features/auth/hooks/useAuth.ts +84 -0
- package/template/client/src/features/auth/services/auth.service.ts +52 -0
- package/template/client/src/features/auth/store/authSlice.ts +38 -0
- package/template/client/src/features/auth/types/auth.types.ts +32 -0
- package/template/client/src/hooks/useDebounce.ts +14 -0
- package/template/client/src/hooks/useLocalStorage.ts +55 -0
- package/template/client/src/hooks/useMediaQuery.ts +27 -0
- package/template/client/src/lib/api/api.types.ts +34 -0
- package/template/client/src/lib/api/axios.config.ts +98 -0
- package/template/client/src/lib/constants/api-endpoints.ts +18 -0
- package/template/client/src/lib/constants/app.constants.ts +12 -0
- package/template/client/src/lib/constants/routes.ts +9 -0
- package/template/client/src/lib/utils/error.ts +32 -0
- package/template/client/src/lib/utils/format.ts +37 -0
- package/template/client/src/lib/utils/security.ts +34 -0
- package/template/client/src/lib/utils.ts +6 -0
- package/template/client/src/middleware.ts +57 -0
- package/template/client/src/store/hooks.ts +7 -0
- package/template/client/src/store/index.ts +12 -0
- package/template/client/src/types/index.ts +3 -0
- package/template/client/tsconfig.json +34 -0
- package/template/gitignore +34 -0
- package/template/server/.dockerignore +66 -0
- package/template/server/.env.example +96 -69
- package/template/server/.env.production.example +90 -0
- package/template/server/Dockerfile +94 -0
- package/template/server/docker-compose.yml +80 -111
- package/template/server/docs/logging.md +62 -0
- package/template/server/eslint.config.mjs +17 -0
- package/template/server/package.json +68 -81
- package/template/server/phpmyadmin-config.php +26 -0
- package/template/server/postman_collection.json +666 -0
- package/template/server/prisma/schema.prisma +77 -93
- package/template/server/prisma/seed.ts +46 -142
- package/template/server/scripts/flush-redis.ts +41 -0
- package/template/server/src/app.ts +243 -71
- package/template/server/src/config/env.ts +67 -94
- package/template/server/src/libs/auth.ts +88 -0
- package/template/server/src/libs/cleanup.ts +35 -0
- package/template/server/src/libs/cookies.ts +46 -0
- package/template/server/src/libs/logger.ts +33 -60
- package/template/server/src/libs/monitoring.ts +205 -0
- package/template/server/src/libs/password.ts +38 -0
- package/template/server/src/libs/prisma.ts +68 -0
- package/template/server/src/libs/redis.ts +60 -79
- package/template/server/src/libs/requestLogger.ts +66 -0
- package/template/server/src/libs/storage/file-storage.service.ts +211 -0
- package/template/server/src/libs/storage/file-validator.ts +97 -0
- package/template/server/src/libs/storage/filename-sanitizer.ts +71 -0
- package/template/server/src/libs/storage/image-optimizer.service.ts +144 -0
- package/template/server/src/modules/auth/__tests__/auth.service.test.ts +365 -0
- package/template/server/src/modules/auth/auth.controller.ts +90 -141
- package/template/server/src/modules/auth/auth.repo.ts +120 -218
- package/template/server/src/modules/auth/auth.routes.ts +96 -83
- package/template/server/src/modules/auth/auth.schemas.ts +35 -137
- package/template/server/src/modules/auth/auth.service.ts +286 -329
- package/template/server/src/modules/auth/session.repo.ts +110 -0
- package/template/server/src/modules/users/users.controller.ts +120 -0
- package/template/server/src/modules/users/users.repo.ts +77 -0
- package/template/server/src/modules/users/users.routes.ts +89 -0
- package/template/server/src/modules/users/users.schemas.ts +21 -0
- package/template/server/src/modules/users/users.service.ts +169 -0
- package/template/server/src/server.ts +58 -139
- package/template/server/src/shared/errors/AppError.ts +21 -0
- package/template/server/src/shared/errors/errors.ts +43 -0
- package/template/server/src/shared/responses/paginatedResponse.ts +38 -0
- package/template/server/src/shared/responses/successResponse.ts +17 -0
- package/template/server/src/shared/schemas/pagination.schema.ts +12 -0
- package/template/server/src/shared/types/index.ts +26 -0
- package/template/server/src/test/setup.ts +74 -38
- package/template/server/tsconfig.json +27 -89
- package/template/server/uploads/avatars/.gitkeep +1 -0
- package/template/server/vitest.config.ts +43 -98
- package/template/.agent/rules/client/01-project-structure.md +0 -326
- package/template/.agent/rules/client/02-component-patterns.md +0 -249
- package/template/.agent/rules/client/03-typescript-rules.md +0 -226
- package/template/.agent/rules/client/04-state-management.md +0 -474
- package/template/.agent/rules/client/05-api-integration.md +0 -129
- package/template/.agent/rules/client/06-forms-validation.md +0 -129
- package/template/.agent/rules/client/07-common-patterns.md +0 -150
- package/template/.agent/rules/client/08-color-system.md +0 -93
- package/template/.agent/rules/client/09-security-rules.md +0 -97
- package/template/.agent/rules/client/10-testing-strategy.md +0 -370
- package/template/.agent/rules/global/ai-edit-safety.md +0 -38
- package/template/.agent/rules/server/01-db-and-migrations.md +0 -242
- package/template/.agent/rules/server/02-general-rules.md +0 -111
- package/template/.agent/rules/server/03-migrations.md +0 -20
- package/template/.agent/rules/server/04-pagination.md +0 -130
- package/template/.agent/rules/server/05-project-conventions.md +0 -71
- package/template/.agent/rules/server/06-response-handling.md +0 -173
- package/template/.agent/rules/server/07-testing-strategy.md +0 -506
- package/template/.agent/rules/server/08-observability.md +0 -180
- package/template/.agent/rules/server/10-background-jobs-v2.md +0 -185
- package/template/.agent/rules/server/11-rate-limiting-v2.md +0 -210
- package/template/.agent/rules/server/12-performance-optimization.md +0 -567
- package/template/.claude/rules/client-01-project-structure.md +0 -327
- package/template/.claude/rules/client-02-component-patterns.md +0 -250
- package/template/.claude/rules/client-03-typescript-rules.md +0 -227
- package/template/.claude/rules/client-04-state-management.md +0 -475
- package/template/.claude/rules/client-05-api-integration.md +0 -130
- package/template/.claude/rules/client-06-forms-validation.md +0 -130
- package/template/.claude/rules/client-07-common-patterns.md +0 -151
- package/template/.claude/rules/client-08-color-system.md +0 -94
- package/template/.claude/rules/client-09-security-rules.md +0 -98
- package/template/.claude/rules/client-10-testing-strategy.md +0 -371
- package/template/.claude/rules/global-ai-edit-safety.md +0 -39
- package/template/.claude/rules/server-01-db-and-migrations.md +0 -243
- package/template/.claude/rules/server-02-general-rules.md +0 -112
- package/template/.claude/rules/server-03-migrations.md +0 -21
- package/template/.claude/rules/server-04-pagination.md +0 -131
- package/template/.claude/rules/server-05-project-conventions.md +0 -72
- package/template/.claude/rules/server-06-response-handling.md +0 -174
- package/template/.claude/rules/server-07-testing-strategy.md +0 -507
- package/template/.claude/rules/server-08-observability.md +0 -181
- package/template/.claude/rules/server-10-background-jobs-v2.md +0 -186
- package/template/.claude/rules/server-11-rate-limiting-v2.md +0 -211
- package/template/.claude/rules/server-12-performance-optimization.md +0 -568
- package/template/.cursor/rules/client-01-project-structure.mdc +0 -327
- package/template/.cursor/rules/client-02-component-patterns.mdc +0 -250
- package/template/.cursor/rules/client-03-typescript-rules.mdc +0 -227
- package/template/.cursor/rules/client-04-state-management.mdc +0 -475
- package/template/.cursor/rules/client-05-api-integration.mdc +0 -130
- package/template/.cursor/rules/client-06-forms-validation.mdc +0 -130
- package/template/.cursor/rules/client-07-common-patterns.mdc +0 -151
- package/template/.cursor/rules/client-08-color-system.mdc +0 -94
- package/template/.cursor/rules/client-09-security-rules.mdc +0 -98
- package/template/.cursor/rules/client-10-testing-strategy.mdc +0 -371
- package/template/.cursor/rules/global-ai-edit-safety.mdc +0 -39
- package/template/.cursor/rules/server-01-db-and-migrations.mdc +0 -243
- package/template/.cursor/rules/server-02-general-rules.mdc +0 -112
- package/template/.cursor/rules/server-03-migrations.mdc +0 -21
- package/template/.cursor/rules/server-04-pagination.mdc +0 -131
- package/template/.cursor/rules/server-05-project-conventions.mdc +0 -72
- package/template/.cursor/rules/server-06-response-handling.mdc +0 -174
- package/template/.cursor/rules/server-07-testing-strategy.mdc +0 -507
- package/template/.cursor/rules/server-08-observability.mdc +0 -181
- package/template/.cursor/rules/server-09-api-documentation-v2.mdc +0 -169
- package/template/.cursor/rules/server-10-background-jobs-v2.mdc +0 -186
- package/template/.cursor/rules/server-11-rate-limiting-v2.mdc +0 -211
- package/template/.cursor/rules/server-12-performance-optimization.mdc +0 -568
- package/template/CLAUDE.md +0 -207
- package/template/server/.tsc-aliasrc.json +0 -12
- package/template/server/README.md +0 -183
- package/template/server/SECURITY.md +0 -190
- package/template/server/Tigra-API.postman_collection.json +0 -733
- package/template/server/biome.json +0 -42
- package/template/server/scripts/setup-env.js +0 -50
- package/template/server/scripts/wait-for-db.js +0 -60
- package/template/server/src/hooks/request-timing.hook.ts +0 -26
- package/template/server/src/libs/auth/authenticate.middleware.ts +0 -22
- package/template/server/src/libs/auth/rbac.middleware.test.ts +0 -134
- package/template/server/src/libs/auth/rbac.middleware.ts +0 -147
- package/template/server/src/libs/db.ts +0 -76
- package/template/server/src/libs/error-handler.ts +0 -89
- package/template/server/src/libs/queue.ts +0 -79
- package/template/server/src/modules/admin/admin.controller.ts +0 -122
- package/template/server/src/modules/admin/admin.routes.ts +0 -62
- package/template/server/src/modules/admin/admin.schemas.ts +0 -35
- package/template/server/src/modules/admin/admin.service.ts +0 -167
- package/template/server/src/modules/auth/auth.integration.test.ts +0 -150
- package/template/server/src/modules/auth/auth.service.test.ts +0 -119
- package/template/server/src/modules/auth/auth.types.ts +0 -97
- package/template/server/src/modules/resources/resources.controller.ts +0 -218
- package/template/server/src/modules/resources/resources.repo.ts +0 -253
- package/template/server/src/modules/resources/resources.routes.ts +0 -116
- package/template/server/src/modules/resources/resources.schemas.ts +0 -146
- package/template/server/src/modules/resources/resources.service.ts +0 -218
- package/template/server/src/modules/resources/resources.types.ts +0 -73
- package/template/server/src/plugins/rate-limit.plugin.ts +0 -21
- package/template/server/src/plugins/security.plugin.ts +0 -21
- package/template/server/src/routes/health.routes.ts +0 -31
- package/template/server/src/types/fastify.d.ts +0 -36
- package/template/server/src/utils/errors.ts +0 -108
- package/template/server/src/utils/pagination.ts +0 -120
- package/template/server/src/utils/response.ts +0 -110
- package/template/server/src/workers/file.worker.ts +0 -106
- package/template/server/tsconfig.build.json +0 -30
- package/template/server/tsconfig.test.json +0 -22
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Resources Routes
|
|
3
|
-
*
|
|
4
|
-
* Fastify route definitions for resources endpoints.
|
|
5
|
-
* Includes rate limiting for security.
|
|
6
|
-
*
|
|
7
|
-
* @see /mnt/project/11-rate-limiting-v2.md
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import type { FastifyInstance } from 'fastify';
|
|
11
|
-
import * as resourceController from './resources.controller';
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Register resources routes
|
|
15
|
-
*
|
|
16
|
-
* @param fastify - Fastify instance
|
|
17
|
-
*/
|
|
18
|
-
export async function resourceRoutes(fastify: FastifyInstance): Promise<void> {
|
|
19
|
-
/**
|
|
20
|
-
* GET /resources
|
|
21
|
-
*
|
|
22
|
-
* Get paginated list of resources with optional filters
|
|
23
|
-
*/
|
|
24
|
-
fastify.get('/', {
|
|
25
|
-
config: {
|
|
26
|
-
rateLimit: {
|
|
27
|
-
max: 100,
|
|
28
|
-
timeWindow: '15 minutes',
|
|
29
|
-
},
|
|
30
|
-
},
|
|
31
|
-
handler: resourceController.listResources,
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* GET /resources/my
|
|
36
|
-
*
|
|
37
|
-
* Get current user's resources
|
|
38
|
-
* Requires authentication
|
|
39
|
-
*/
|
|
40
|
-
fastify.get('/my', {
|
|
41
|
-
config: {
|
|
42
|
-
rateLimit: {
|
|
43
|
-
max: 1000,
|
|
44
|
-
timeWindow: '15 minutes',
|
|
45
|
-
},
|
|
46
|
-
},
|
|
47
|
-
preHandler: [fastify.authenticate, fastify.requireAny()],
|
|
48
|
-
handler: resourceController.getMyResources,
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* GET /resources/:id
|
|
53
|
-
*
|
|
54
|
-
* Get single resource by ID with owner information
|
|
55
|
-
*/
|
|
56
|
-
fastify.get('/:id', {
|
|
57
|
-
config: {
|
|
58
|
-
rateLimit: {
|
|
59
|
-
max: 100,
|
|
60
|
-
timeWindow: '15 minutes',
|
|
61
|
-
},
|
|
62
|
-
},
|
|
63
|
-
handler: resourceController.getResource,
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* POST /resources
|
|
68
|
-
*
|
|
69
|
-
* Create a new resource
|
|
70
|
-
* Requires authentication
|
|
71
|
-
*/
|
|
72
|
-
fastify.post('/', {
|
|
73
|
-
config: {
|
|
74
|
-
rateLimit: {
|
|
75
|
-
max: 1000,
|
|
76
|
-
timeWindow: '15 minutes',
|
|
77
|
-
},
|
|
78
|
-
},
|
|
79
|
-
preHandler: [fastify.authenticate, fastify.requireAny()],
|
|
80
|
-
handler: resourceController.createResource,
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* PATCH /resources/:id
|
|
85
|
-
*
|
|
86
|
-
* Update a resource (owner only)
|
|
87
|
-
* Requires authentication
|
|
88
|
-
*/
|
|
89
|
-
fastify.patch('/:id', {
|
|
90
|
-
config: {
|
|
91
|
-
rateLimit: {
|
|
92
|
-
max: 1000,
|
|
93
|
-
timeWindow: '15 minutes',
|
|
94
|
-
},
|
|
95
|
-
},
|
|
96
|
-
preHandler: [fastify.authenticate, fastify.requireAny()],
|
|
97
|
-
handler: resourceController.updateResource,
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* DELETE /resources/:id
|
|
102
|
-
*
|
|
103
|
-
* Delete a resource (owner only, soft delete)
|
|
104
|
-
* Requires authentication
|
|
105
|
-
*/
|
|
106
|
-
fastify.delete('/:id', {
|
|
107
|
-
config: {
|
|
108
|
-
rateLimit: {
|
|
109
|
-
max: 1000,
|
|
110
|
-
timeWindow: '15 minutes',
|
|
111
|
-
},
|
|
112
|
-
},
|
|
113
|
-
preHandler: [fastify.authenticate, fastify.requireAny()],
|
|
114
|
-
handler: resourceController.deleteResource,
|
|
115
|
-
});
|
|
116
|
-
}
|
|
@@ -1,146 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Resources Schemas
|
|
3
|
-
*
|
|
4
|
-
* Zod schemas for request validation and type validation.
|
|
5
|
-
* Ensures type safety and API documentation consistency.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { z } from 'zod';
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Create Resource Schema
|
|
12
|
-
*
|
|
13
|
-
* Validates resource creation requests.
|
|
14
|
-
*/
|
|
15
|
-
export const CreateResourceSchema = z.object({
|
|
16
|
-
title: z
|
|
17
|
-
.string()
|
|
18
|
-
.min(1, 'Title is required')
|
|
19
|
-
.max(200, 'Title must not exceed 200 characters')
|
|
20
|
-
.trim(),
|
|
21
|
-
summary: z
|
|
22
|
-
.string()
|
|
23
|
-
.max(1000, 'Summary must not exceed 1000 characters')
|
|
24
|
-
.trim()
|
|
25
|
-
.optional(),
|
|
26
|
-
price: z
|
|
27
|
-
.number()
|
|
28
|
-
.positive('Price must be positive')
|
|
29
|
-
.multipleOf(0.01, 'Price can have at most 2 decimal places')
|
|
30
|
-
.max(999999.99, 'Price is too large'),
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Update Resource Schema
|
|
35
|
-
*
|
|
36
|
-
* Validates resource update requests.
|
|
37
|
-
* All fields are optional (partial update).
|
|
38
|
-
*/
|
|
39
|
-
export const UpdateResourceSchema = z.object({
|
|
40
|
-
title: z
|
|
41
|
-
.string()
|
|
42
|
-
.min(1, 'Title cannot be empty')
|
|
43
|
-
.max(200, 'Title must not exceed 200 characters')
|
|
44
|
-
.trim()
|
|
45
|
-
.optional(),
|
|
46
|
-
summary: z
|
|
47
|
-
.string()
|
|
48
|
-
.max(1000, 'Summary must not exceed 1000 characters')
|
|
49
|
-
.trim()
|
|
50
|
-
.optional(),
|
|
51
|
-
price: z
|
|
52
|
-
.number()
|
|
53
|
-
.positive('Price must be positive')
|
|
54
|
-
.multipleOf(0.01, 'Price can have at most 2 decimal places')
|
|
55
|
-
.max(999999.99, 'Price is too large')
|
|
56
|
-
.optional(),
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Resource Filters Schema
|
|
61
|
-
*
|
|
62
|
-
* Validates query parameters for filtering resources.
|
|
63
|
-
*/
|
|
64
|
-
export const ResourceFiltersSchema = z.object({
|
|
65
|
-
status: z
|
|
66
|
-
.enum(['active', 'inactive', 'deleted'])
|
|
67
|
-
.optional(),
|
|
68
|
-
minPrice: z
|
|
69
|
-
.coerce
|
|
70
|
-
.number()
|
|
71
|
-
.positive('Minimum price must be positive')
|
|
72
|
-
.optional(),
|
|
73
|
-
maxPrice: z
|
|
74
|
-
.coerce
|
|
75
|
-
.number()
|
|
76
|
-
.positive('Maximum price must be positive')
|
|
77
|
-
.optional(),
|
|
78
|
-
ownerId: z
|
|
79
|
-
.string()
|
|
80
|
-
.uuid('Invalid owner ID format')
|
|
81
|
-
.optional(),
|
|
82
|
-
search: z
|
|
83
|
-
.string()
|
|
84
|
-
.trim()
|
|
85
|
-
.optional(),
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Pagination Schema
|
|
90
|
-
*
|
|
91
|
-
* Validates pagination query parameters.
|
|
92
|
-
*/
|
|
93
|
-
export const PaginationSchema = z.object({
|
|
94
|
-
page: z
|
|
95
|
-
.coerce
|
|
96
|
-
.number()
|
|
97
|
-
.int()
|
|
98
|
-
.min(1, 'Page must be at least 1')
|
|
99
|
-
.default(1),
|
|
100
|
-
limit: z
|
|
101
|
-
.coerce
|
|
102
|
-
.number()
|
|
103
|
-
.int()
|
|
104
|
-
.min(1, 'Limit must be at least 1')
|
|
105
|
-
.max(100, 'Limit must not exceed 100')
|
|
106
|
-
.default(10),
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Resource Response Schema (for type validation)
|
|
111
|
-
*
|
|
112
|
-
* Defines the structure of resource objects in API responses.
|
|
113
|
-
*/
|
|
114
|
-
export const ResourceResponseSchema = z.object({
|
|
115
|
-
id: z.string().uuid(),
|
|
116
|
-
ownerId: z.string().uuid(),
|
|
117
|
-
title: z.string(),
|
|
118
|
-
summary: z.string().nullable(),
|
|
119
|
-
price: z.number(),
|
|
120
|
-
status: z.string(),
|
|
121
|
-
createdAt: z.string().datetime(),
|
|
122
|
-
updatedAt: z.string().datetime(),
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* Resource with Owner Response Schema (for type validation)
|
|
127
|
-
*/
|
|
128
|
-
export const ResourceWithOwnerResponseSchema = ResourceResponseSchema.extend({
|
|
129
|
-
owner: z.object({
|
|
130
|
-
id: z.string().uuid(),
|
|
131
|
-
email: z.string().email(),
|
|
132
|
-
name: z.string().nullable(),
|
|
133
|
-
}),
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
/**
|
|
137
|
-
* Type inference from schemas
|
|
138
|
-
*
|
|
139
|
-
* These types can be used in controllers and services.
|
|
140
|
-
*/
|
|
141
|
-
export type CreateResourceInput = z.infer<typeof CreateResourceSchema>;
|
|
142
|
-
export type UpdateResourceInput = z.infer<typeof UpdateResourceSchema>;
|
|
143
|
-
export type ResourceFiltersInput = z.infer<typeof ResourceFiltersSchema>;
|
|
144
|
-
export type PaginationInput = z.infer<typeof PaginationSchema>;
|
|
145
|
-
export type ResourceResponse = z.infer<typeof ResourceResponseSchema>;
|
|
146
|
-
export type ResourceWithOwnerResponse = z.infer<typeof ResourceWithOwnerResponseSchema>;
|
|
@@ -1,218 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Resources Service
|
|
3
|
-
*
|
|
4
|
-
* Business logic for resources operations.
|
|
5
|
-
* Handles CRUD operations with authorization checks.
|
|
6
|
-
*
|
|
7
|
-
* @see /mnt/project/02-general-rules.md
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import logger from '@/libs/logger';
|
|
11
|
-
import { NotFoundError, ForbiddenError } from '@/utils/errors';
|
|
12
|
-
import * as resourceRepo from './resources.repo';
|
|
13
|
-
import type { ResourceFilters, Resource, ResourceWithOwner } from './resources.types';
|
|
14
|
-
import type { CreateResourceInput, UpdateResourceInput } from './resources.schemas';
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Service response for paginated data
|
|
18
|
-
*/
|
|
19
|
-
interface PaginatedResult<T> {
|
|
20
|
-
items: T[];
|
|
21
|
-
totalItems: number;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Get resources with filters and pagination
|
|
26
|
-
*
|
|
27
|
-
* @param filters - Filter criteria
|
|
28
|
-
* @param page - Page number (1-indexed)
|
|
29
|
-
* @param limit - Items per page
|
|
30
|
-
* @returns Items and total count
|
|
31
|
-
*/
|
|
32
|
-
export async function getResources(
|
|
33
|
-
filters: ResourceFilters,
|
|
34
|
-
page: number,
|
|
35
|
-
limit: number
|
|
36
|
-
): Promise<PaginatedResult<Resource>> {
|
|
37
|
-
try {
|
|
38
|
-
// Get items with filters and pagination
|
|
39
|
-
const items = await resourceRepo.findMany(filters, page, limit);
|
|
40
|
-
|
|
41
|
-
// Get total count with same filters
|
|
42
|
-
const totalItems = await resourceRepo.count(filters);
|
|
43
|
-
|
|
44
|
-
logger.info(
|
|
45
|
-
{ filters, page, limit, count: items.length, totalItems },
|
|
46
|
-
'Resources retrieved'
|
|
47
|
-
);
|
|
48
|
-
|
|
49
|
-
return { items, totalItems };
|
|
50
|
-
} catch (error) {
|
|
51
|
-
logger.error({ error, filters, page, limit }, 'Failed to get resources');
|
|
52
|
-
throw error;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Get resource by ID
|
|
58
|
-
*
|
|
59
|
-
* @param id - Resource ID
|
|
60
|
-
* @returns Resource with owner relation
|
|
61
|
-
* @throws NotFoundError if resource not found
|
|
62
|
-
*/
|
|
63
|
-
export async function getResource(id: string): Promise<ResourceWithOwner> {
|
|
64
|
-
try {
|
|
65
|
-
const resource = await resourceRepo.findById(id);
|
|
66
|
-
|
|
67
|
-
if (!resource) {
|
|
68
|
-
throw new NotFoundError('Resource not found');
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
logger.info({ resourceId: id }, 'Resource retrieved');
|
|
72
|
-
|
|
73
|
-
return resource;
|
|
74
|
-
} catch (error) {
|
|
75
|
-
logger.error({ error, resourceId: id }, 'Failed to get resource');
|
|
76
|
-
throw error;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Create a new resource
|
|
82
|
-
*
|
|
83
|
-
* @param data - Resource creation data
|
|
84
|
-
* @param userId - Owner user ID
|
|
85
|
-
* @returns Created resource
|
|
86
|
-
*/
|
|
87
|
-
export async function createResource(
|
|
88
|
-
data: CreateResourceInput,
|
|
89
|
-
userId: string
|
|
90
|
-
): Promise<Resource> {
|
|
91
|
-
try {
|
|
92
|
-
const resource = await resourceRepo.create(data, userId);
|
|
93
|
-
|
|
94
|
-
logger.info(
|
|
95
|
-
{ resourceId: resource.id, userId, title: resource.title },
|
|
96
|
-
'Resource created'
|
|
97
|
-
);
|
|
98
|
-
|
|
99
|
-
return resource;
|
|
100
|
-
} catch (error) {
|
|
101
|
-
logger.error({ error, userId, data }, 'Failed to create resource');
|
|
102
|
-
throw error;
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* Update a resource
|
|
108
|
-
*
|
|
109
|
-
* @param id - Resource ID
|
|
110
|
-
* @param userId - User ID (for ownership verification)
|
|
111
|
-
* @param data - Update data
|
|
112
|
-
* @returns Updated resource
|
|
113
|
-
* @throws NotFoundError if resource not found
|
|
114
|
-
* @throws ForbiddenError if user is not the owner
|
|
115
|
-
*/
|
|
116
|
-
export async function updateResource(
|
|
117
|
-
id: string,
|
|
118
|
-
userId: string,
|
|
119
|
-
data: UpdateResourceInput
|
|
120
|
-
): Promise<Resource> {
|
|
121
|
-
try {
|
|
122
|
-
// Check if resource exists
|
|
123
|
-
const exists = await resourceRepo.exists(id);
|
|
124
|
-
if (!exists) {
|
|
125
|
-
throw new NotFoundError('Resource not found');
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// Verify ownership
|
|
129
|
-
const isOwner = await resourceRepo.isOwner(id, userId);
|
|
130
|
-
if (!isOwner) {
|
|
131
|
-
throw new ForbiddenError('You do not have permission to update this resource');
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// Update resource
|
|
135
|
-
const resource = await resourceRepo.update(id, data);
|
|
136
|
-
|
|
137
|
-
logger.info(
|
|
138
|
-
{ resourceId: id, userId, updates: Object.keys(data) },
|
|
139
|
-
'Resource updated'
|
|
140
|
-
);
|
|
141
|
-
|
|
142
|
-
return resource;
|
|
143
|
-
} catch (error) {
|
|
144
|
-
logger.error({ error, resourceId: id, userId, data }, 'Failed to update resource');
|
|
145
|
-
throw error;
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
* Delete a resource (soft delete)
|
|
151
|
-
*
|
|
152
|
-
* @param id - Resource ID
|
|
153
|
-
* @param userId - User ID (for ownership verification)
|
|
154
|
-
* @returns Deleted resource
|
|
155
|
-
* @throws NotFoundError if resource not found
|
|
156
|
-
* @throws ForbiddenError if user is not the owner
|
|
157
|
-
*/
|
|
158
|
-
export async function deleteResource(
|
|
159
|
-
id: string,
|
|
160
|
-
userId: string
|
|
161
|
-
): Promise<Resource> {
|
|
162
|
-
try {
|
|
163
|
-
// Check if resource exists
|
|
164
|
-
const exists = await resourceRepo.exists(id);
|
|
165
|
-
if (!exists) {
|
|
166
|
-
throw new NotFoundError('Resource not found');
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// Verify ownership
|
|
170
|
-
const isOwner = await resourceRepo.isOwner(id, userId);
|
|
171
|
-
if (!isOwner) {
|
|
172
|
-
throw new ForbiddenError('You do not have permission to delete this resource');
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
// Soft delete resource
|
|
176
|
-
const resource = await resourceRepo.deleteResource(id);
|
|
177
|
-
|
|
178
|
-
logger.info({ resourceId: id, userId }, 'Resource deleted');
|
|
179
|
-
|
|
180
|
-
return resource;
|
|
181
|
-
} catch (error) {
|
|
182
|
-
logger.error({ error, resourceId: id, userId }, 'Failed to delete resource');
|
|
183
|
-
throw error;
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
/**
|
|
188
|
-
* Get user's resources
|
|
189
|
-
*
|
|
190
|
-
* @param userId - User ID
|
|
191
|
-
* @param page - Page number (1-indexed)
|
|
192
|
-
* @param limit - Items per page
|
|
193
|
-
* @returns User's resources and total count
|
|
194
|
-
*/
|
|
195
|
-
export async function getMyResources(
|
|
196
|
-
userId: string,
|
|
197
|
-
page: number,
|
|
198
|
-
limit: number
|
|
199
|
-
): Promise<PaginatedResult<Resource>> {
|
|
200
|
-
try {
|
|
201
|
-
// Filter by owner ID
|
|
202
|
-
const filters: ResourceFilters = { ownerId: userId };
|
|
203
|
-
|
|
204
|
-
// Get items and count
|
|
205
|
-
const items = await resourceRepo.findMany(filters, page, limit);
|
|
206
|
-
const totalItems = await resourceRepo.count(filters);
|
|
207
|
-
|
|
208
|
-
logger.info(
|
|
209
|
-
{ userId, page, limit, count: items.length, totalItems },
|
|
210
|
-
'User resources retrieved'
|
|
211
|
-
);
|
|
212
|
-
|
|
213
|
-
return { items, totalItems };
|
|
214
|
-
} catch (error) {
|
|
215
|
-
logger.error({ error, userId, page, limit }, 'Failed to get user resources');
|
|
216
|
-
throw error;
|
|
217
|
-
}
|
|
218
|
-
}
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Resources Types
|
|
3
|
-
*
|
|
4
|
-
* TypeScript types and interfaces for resources module.
|
|
5
|
-
* Resources are the main CRUD entity in this application.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Resource Status Enum
|
|
10
|
-
*/
|
|
11
|
-
export type ResourceStatus = 'active' | 'inactive' | 'deleted';
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Resource object
|
|
15
|
-
*
|
|
16
|
-
* Main entity representing a resource in the system.
|
|
17
|
-
*/
|
|
18
|
-
export interface Resource {
|
|
19
|
-
id: string;
|
|
20
|
-
ownerId: string;
|
|
21
|
-
title: string;
|
|
22
|
-
summary: string | null;
|
|
23
|
-
price: number;
|
|
24
|
-
status: string;
|
|
25
|
-
createdAt: Date;
|
|
26
|
-
updatedAt: Date;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Create Resource Request Payload
|
|
31
|
-
*/
|
|
32
|
-
export interface CreateResourceRequest {
|
|
33
|
-
title: string;
|
|
34
|
-
summary?: string;
|
|
35
|
-
price: number;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Update Resource Request Payload
|
|
40
|
-
*
|
|
41
|
-
* All fields are optional (partial update)
|
|
42
|
-
*/
|
|
43
|
-
export interface UpdateResourceRequest {
|
|
44
|
-
title?: string;
|
|
45
|
-
summary?: string;
|
|
46
|
-
price?: number;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Resource Filters
|
|
51
|
-
*
|
|
52
|
-
* Used for filtering and searching resources.
|
|
53
|
-
*/
|
|
54
|
-
export interface ResourceFilters {
|
|
55
|
-
status?: ResourceStatus;
|
|
56
|
-
minPrice?: number;
|
|
57
|
-
maxPrice?: number;
|
|
58
|
-
ownerId?: string;
|
|
59
|
-
search?: string; // Search in title
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Resource with Owner
|
|
64
|
-
*
|
|
65
|
-
* Resource with owner relation populated.
|
|
66
|
-
*/
|
|
67
|
-
export interface ResourceWithOwner extends Resource {
|
|
68
|
-
owner: {
|
|
69
|
-
id: string;
|
|
70
|
-
email: string;
|
|
71
|
-
name: string | null;
|
|
72
|
-
};
|
|
73
|
-
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { FastifyInstance } from 'fastify';
|
|
2
|
-
import rateLimit from '@fastify/rate-limit';
|
|
3
|
-
import { redis } from '@/libs/redis';
|
|
4
|
-
import { env } from '@/config/env';
|
|
5
|
-
|
|
6
|
-
export async function registerRateLimit(app: FastifyInstance) {
|
|
7
|
-
await app.register(rateLimit, {
|
|
8
|
-
global: true,
|
|
9
|
-
max: env.RATE_LIMIT_MAX,
|
|
10
|
-
timeWindow: env.RATE_LIMIT_WINDOW,
|
|
11
|
-
redis,
|
|
12
|
-
nameSpace: 'rate-limit:',
|
|
13
|
-
errorResponseBuilder: (request, context) => ({
|
|
14
|
-
success: false,
|
|
15
|
-
error: {
|
|
16
|
-
code: 'RATE_LIMIT_EXCEEDED',
|
|
17
|
-
message: `Too many requests. Please try again in ${Math.ceil(context.ttl / 1000)}s.`,
|
|
18
|
-
},
|
|
19
|
-
}),
|
|
20
|
-
});
|
|
21
|
-
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { FastifyInstance } from 'fastify';
|
|
2
|
-
import cors from '@fastify/cors';
|
|
3
|
-
import helmet from '@fastify/helmet';
|
|
4
|
-
import compress from '@fastify/compress';
|
|
5
|
-
import { env } from '@/config/env';
|
|
6
|
-
|
|
7
|
-
export async function registerSecurityPlugins(app: FastifyInstance) {
|
|
8
|
-
// CORS - Cross-Origin Resource Sharing
|
|
9
|
-
await app.register(cors, {
|
|
10
|
-
origin: env.NODE_ENV === 'development' ? true : env.CORS_ALLOWED_ORIGINS.split(','),
|
|
11
|
-
credentials: true,
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
// Helmet - Security headers
|
|
15
|
-
await app.register(helmet, {
|
|
16
|
-
contentSecurityPolicy: env.NODE_ENV === 'production',
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
// Compression - Gzip/Deflate
|
|
20
|
-
await app.register(compress, { global: true });
|
|
21
|
-
}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { FastifyInstance } from 'fastify';
|
|
2
|
-
import { prisma } from '@/libs/db';
|
|
3
|
-
import { redis } from '@/libs/redis';
|
|
4
|
-
|
|
5
|
-
export async function healthRoutes(app: FastifyInstance) {
|
|
6
|
-
app.get('/health', async (request, reply) => {
|
|
7
|
-
const health = {
|
|
8
|
-
status: 'healthy',
|
|
9
|
-
timestamp: new Date().toISOString(),
|
|
10
|
-
uptime: process.uptime(),
|
|
11
|
-
database: 'connected',
|
|
12
|
-
redis: 'connected',
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
try {
|
|
16
|
-
await prisma.$queryRaw`SELECT 1`;
|
|
17
|
-
} catch (error) {
|
|
18
|
-
health.database = 'disconnected';
|
|
19
|
-
health.status = 'unhealthy';
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
try {
|
|
23
|
-
await redis.ping();
|
|
24
|
-
} catch (error) {
|
|
25
|
-
health.redis = 'disconnected';
|
|
26
|
-
health.status = 'unhealthy';
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
return reply.status(health.status === 'healthy' ? 200 : 503).send(health);
|
|
30
|
-
});
|
|
31
|
-
}
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { FastifyReply, FastifyRequest } from 'fastify';
|
|
2
|
-
import { JwtPayload } from '@/modules/auth/auth.types';
|
|
3
|
-
import { UserRole } from '@/libs/auth/rbac.middleware';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* TypeScript Declaration Merging
|
|
7
|
-
*
|
|
8
|
-
* Extends Fastify types with custom properties.
|
|
9
|
-
*/
|
|
10
|
-
declare module 'fastify' {
|
|
11
|
-
interface FastifyRequest {
|
|
12
|
-
user?: JwtPayload;
|
|
13
|
-
startTime: number;
|
|
14
|
-
}
|
|
15
|
-
interface FastifyInstance {
|
|
16
|
-
authenticate: (
|
|
17
|
-
request: FastifyRequest,
|
|
18
|
-
reply: FastifyReply
|
|
19
|
-
) => Promise<void>;
|
|
20
|
-
requireRole: (
|
|
21
|
-
...roles: UserRole[]
|
|
22
|
-
) => (request: FastifyRequest, reply: FastifyReply) => Promise<void>;
|
|
23
|
-
requireAdmin: () => (
|
|
24
|
-
request: FastifyRequest,
|
|
25
|
-
reply: FastifyReply
|
|
26
|
-
) => Promise<void>;
|
|
27
|
-
requireUser: () => (
|
|
28
|
-
request: FastifyRequest,
|
|
29
|
-
reply: FastifyReply
|
|
30
|
-
) => Promise<void>;
|
|
31
|
-
requireAny: () => (
|
|
32
|
-
request: FastifyRequest,
|
|
33
|
-
reply: FastifyReply
|
|
34
|
-
) => Promise<void>;
|
|
35
|
-
}
|
|
36
|
-
}
|